<?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: Jack Lewis</title>
    <description>The latest articles on DEV Community by Jack Lewis (@jlewis92).</description>
    <link>https://dev.to/jlewis92</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%2F1024561%2F38b7f23d-5ff7-46e2-a15e-f6fa41fd50d0.png</url>
      <title>DEV Community: Jack Lewis</title>
      <link>https://dev.to/jlewis92</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jlewis92"/>
    <language>en</language>
    <item>
      <title>Setting up a simple testing project with C#</title>
      <dc:creator>Jack Lewis</dc:creator>
      <pubDate>Fri, 26 May 2023 21:31:24 +0000</pubDate>
      <link>https://dev.to/jlewis92/setting-up-a-simple-testing-project-with-c-4n81</link>
      <guid>https://dev.to/jlewis92/setting-up-a-simple-testing-project-with-c-4n81</guid>
      <description>&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This article is an attempt to showcase how to set up testing in C#, and doesn't represent some "standard" practices observed when unit testing (such as TDD).  This is so the article can more easily explain the process in a way that flows easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;Testing is extremely important in any programming language.  However, there's so many ways to do it, so I thought I'd write a short article on how I set up testing in C#.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;To start with you need to choose what type of project you want to work with.  This could be a website using MVC, or a service or something like a desktop application using WPF.  For the case of this demo, I'm going to use a console application as it's reasonably straightforward. You can do this by going Visual Studio and choosing a project type you want.  In this case I want to use .Net 6, so I'm picking the one which doesn't say (.Net Framework):&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%2F7t6a35lhzz0pyqkc05up.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7t6a35lhzz0pyqkc05up.png" alt="Visual Studio" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After naming the project, you get an option of using the "new" style of writing a C# program using Top-level statements, or going with the more classic version of choosing a project.  For the purposes of this demo, I'm going with the classic way of building &lt;code&gt;Program.cs&lt;/code&gt; as I tend to find it a little confusing to show how C# works to a beginner.  So for this I'm clicking the checkbox &lt;code&gt;Do not use top level statements&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6loccvqjnuc3ggjqwuy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6loccvqjnuc3ggjqwuy.png" alt="no top level statements" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just for context, if you chose to use top level statements it would look like this:&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;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, World!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By contrast, this is an example of what it looks like without:&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;namespace&lt;/span&gt; &lt;span class="nn"&gt;SimpleTestingApp&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
    &lt;span class="p"&gt;{&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;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&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="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, World!"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we're going to throw in our &lt;em&gt;first&lt;/em&gt; test project to be able to cover this program.  We can do that by right-clicking on the solution and going to Add &amp;gt; New Project&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%2Fuezjuf7r6withrjjf4ln.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuezjuf7r6withrjjf4ln.png" alt="Adding a project" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point you're going to see a familiar screen asking you to select a project.  Here we're looking for a test project.  By default, Visual Studio gives you access to 3 different testing frameworks based on your choice of project.  These are &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest" rel="noopener noreferrer"&gt;MSTest&lt;/a&gt;, &lt;a href="https://xunit.net/" rel="noopener noreferrer"&gt;XUnit&lt;/a&gt; and &lt;a href="https://nunit.org/" rel="noopener noreferrer"&gt;NUnit&lt;/a&gt;.  Ultimately, all 3 of these testing accomplish the same thing, and I've worked with all of them at various points in my career.  The difference is mainly in exact syntax and documentation.  Although, it's generally considered that MSTest is a little "older" than NUnit or XUnit, so I tend to see it less now.  For the purposes of this demo, I'm going to go with NUnit:&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%2F18j4j0bbyxbjp4zp05wn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18j4j0bbyxbjp4zp05wn.png" alt="NUnit" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point we just hit next, and we get an option to name the project.  In C#, the naming convention is usually &lt;code&gt;&amp;lt;project name&amp;gt;.Test&lt;/code&gt; as our test projects are designed to be directly related to the project we want to test.  In this case, as the project I want to test is called &lt;code&gt;SimpleTestingApp&lt;/code&gt;, my test project is &lt;code&gt;SimpleTestingApp.Test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At this point a project should be created a default test file and global using file containing NUnit:&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%2Fxixook72dazqym90o91o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxixook72dazqym90o91o.png" alt="Simple test project" width="223" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now at this stage we &lt;em&gt;could&lt;/em&gt; start writing tests, but in reality we normally abstract code out of &lt;code&gt;Program.cs&lt;/code&gt; as much as possible because it's static and difficult to test. While we could just create some extra files with the &lt;code&gt;SimpleTestingApp&lt;/code&gt; project, it's far more likely to create a separate project that contains most of the code.  In this case I'm going to create a &lt;code&gt;SimpleBankingApp&lt;/code&gt; project.  Given I want to unit test all of my code, what I tend to do is try and group my test projects with my "real" code.  To do this, I right-click on the solution in Solution explorer and go to &lt;code&gt;add &amp;gt; New solution folder&lt;/code&gt; and name the folder &lt;code&gt;SimpleBankingApp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is directory I'll create my new projects in, which in this case is a class library and a test project.  Once you've done this, your solution explorer should look something like this:&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%2Ftt01xy9gz7248b3i0qqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftt01xy9gz7248b3i0qqf.png" alt="Solution explorer" width="365" height="161"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, Visual Studio automatically changes the icon of the project based on what it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project to test
&lt;/h2&gt;

&lt;p&gt;So at this point I'm just going to write some code as this section isn't that important, it's testing that we're interested in.  For context, this is what my application looks like:&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%2F8mpis7ezqc0knhrtyspk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8mpis7ezqc0knhrtyspk.png" alt="Simple banking app" width="320" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and some of the more important classes:&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;interface&lt;/span&gt; &lt;span class="nc"&gt;IInterestCalculator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;CalculateInterest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IAccount&lt;/span&gt; &lt;span class="n"&gt;account&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;class&lt;/span&gt; &lt;span class="nc"&gt;InterestCalculator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IInterestCalculator&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;readonly&lt;/span&gt; &lt;span class="n"&gt;IRateCalculator&lt;/span&gt; &lt;span class="n"&gt;_standardRateCalculator&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;InterestCalculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IRateCalculator&lt;/span&gt; &lt;span class="n"&gt;standardRateCalculator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_standardRateCalculator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;standardRateCalculator&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="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;CalculateInterest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IAccount&lt;/span&gt; &lt;span class="n"&gt;account&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="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;_standardRateCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StandardRate&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IRateCalculator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;StandardRate&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="p"&gt;}&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;StandardRateCalculator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRateCalculator&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;StandardRateCalculator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;StandardRate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="m"&gt;032M&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="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;StandardRate&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="p"&gt;}&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;Customer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Name&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;set&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;TelephoneNumber&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;set&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="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAccount&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Accounts&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;set&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="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAccount&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;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IAccount&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Balance&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;set&lt;/span&gt;&lt;span class="p"&gt;;&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;Age&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;set&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;This is a really straightforward application which I'm going to use as the base for unit testing.  So I'm going to move on to my testing project for setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test project setup
&lt;/h2&gt;

&lt;p&gt;First up, I'm using dependency injection to pass in interfaces to the &lt;code&gt;InterestCalculator&lt;/code&gt; class.  On top of being good practice depend on abstractions, this means my project is perfect for mocking which is the process used to isolate complex pieces of code.  Additionally, one of the primary purposes of mocking is to allow you to run tests without needing to rely on third parties.  For example, when doing web requests or database calls.&lt;/p&gt;

&lt;p&gt;In terms of mocking there are several frameworks you can use, but I've mainly relied on &lt;a href="https://github.com/moq/moq4" rel="noopener noreferrer"&gt;Moq&lt;/a&gt; and &lt;a href="https://nsubstitute.github.io/" rel="noopener noreferrer"&gt;NSubstitute&lt;/a&gt;.  Within this demo, I'm going to use NSubstitute as I've found it a little easier to use.&lt;/p&gt;

&lt;p&gt;To do this we're just going install it into the test project using the Visual Studio NuGet package manager.  All you need to do is right-click on &lt;code&gt;SimpleBankingApp.Test&lt;/code&gt; and select "Manage NuGet packages" and then search for NSubstitute:&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%2Fkwee7olmqk1sh9n7hhe4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwee7olmqk1sh9n7hhe4.png" alt="NSubstitute in Visual Studio" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing tests
&lt;/h2&gt;

&lt;p&gt;We're now ready to write some tests.  To start with, as it's slightly more complicated than anything else, I'm going to write some tests for the &lt;code&gt;InterestCalculator&lt;/code&gt; class.  To do this, I'm going to just create a folder called &lt;code&gt;Unit&lt;/code&gt; and then a class within that folder called &lt;code&gt;InterestCalculatorTest&lt;/code&gt; in my test project.  I'll also delete the generate file &lt;code&gt;UnitTest1.cs&lt;/code&gt;:&lt;/p&gt;

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

&lt;p&gt;I can now move onto setting up my tests.  This is straightforward in this case, as I have very little to set up.  Pretty much all I want to do is mock out &lt;code&gt;IRateCalculator&lt;/code&gt; for use throughout the class and as I only need one version of this throughout, I thend to set them up in the constructor:&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;InterestCalculatorTest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;IRateCalculator&lt;/span&gt; &lt;span class="n"&gt;_rateCalculator&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;InterestCalculatorTest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// mocking the rate calculator&lt;/span&gt;
        &lt;span class="n"&gt;_rateCalculator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Substitute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;For&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IRateCalculator&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// telling the rate calculator to return 0.05;&lt;/span&gt;
        &lt;span class="n"&gt;_rateCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StandardRate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.05m&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;If this was to change on a per test basis, I could either construct this object in the test itself, or use a setup method that's run before every test.  For example:&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;SetUp&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;void&lt;/span&gt; &lt;span class="nf"&gt;Setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// mocking the rate calculator&lt;/span&gt;
    &lt;span class="n"&gt;_rateCalculator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Substitute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;For&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IRateCalculator&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// telling the rate calculator to return 0.05;&lt;/span&gt;
    &lt;span class="n"&gt;_rateCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StandardRate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.05m&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;While there's a lot of setup to get to this point, I'm now ready to write my first test.  In this case, I'm going to check that I can calculate an interest.  In order to do that I'll just create a standard void method.  The hardest part now, is how to name a test.  There are lots of different ways of doing this, but I'm a fan of a 3 part test name, which is separated by underscores.  They contain the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The method you're testing&lt;/li&gt;
&lt;li&gt;The expected outcome&lt;/li&gt;
&lt;li&gt;What you're doing to get that outcome.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this case, my test name is going to be &lt;code&gt;CalculateInterest_MakesAnInterestCalculation_WhenCalledWithStandardValue&lt;/code&gt;.  I know this is a bit wordy for the name of a method, but the idea is that it's very clear about what this test is doing.&lt;/p&gt;

&lt;p&gt;Once this is done, I instantly add the following to my test:&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;CalculateInterest_MakesAnInterestCalculation_WhenCalledWithStandardValue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Arrange&lt;/span&gt;

    &lt;span class="c1"&gt;// Act&lt;/span&gt;

    &lt;span class="c1"&gt;// Assert&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is to always remind me of the 3 parts I need to do when unit testing, and indicates to a developer coming after me what each section of the code is doing.  This because large can b e some of the most confusing pieces code to figure because you can need to do a lot of setup.&lt;/p&gt;

&lt;p&gt;Anyway, the parts of a test are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Arrange

&lt;ol&gt;
&lt;li&gt;Anything that needs to be done to set a test up for a run&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Act

&lt;ol&gt;
&lt;li&gt;Where you actually run the method to test&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Assert

&lt;ol&gt;
&lt;li&gt;This is where you check that the response from the &lt;code&gt;Act&lt;/code&gt; step matches what you expect to happen.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;I can then fill out the test to be something like this:&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&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;void&lt;/span&gt; &lt;span class="nf"&gt;CalculateInterest_MakesAnInterestCalculation_WhenCalledWithStandardValue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Arrange&lt;/span&gt;
    &lt;span class="c1"&gt;// I'm setting up an account for the test, so this is arrange&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;account&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;Account&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Age&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="n"&gt;Balance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// I'm constructing the interest calcualtor, which is arrange&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;interestCalculator&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;InterestCalculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_rateCalculator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// I know what the calculation is, so I'm making sure my calculations will match&lt;/span&gt;
    &lt;span class="c1"&gt;// This is usally frowned upon due to creating strong links between code and test&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;expectedInterest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;_rateCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StandardRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Act&lt;/span&gt;
    &lt;span class="c1"&gt;// Running the method I want to run is act&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;interest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interestCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateInterest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Assert&lt;/span&gt;
    &lt;span class="c1"&gt;// Asserting that the interest will be greater than 0&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expectedInterest&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;&lt;strong&gt;NOTE:&lt;/strong&gt; the &lt;code&gt;[Test]&lt;/code&gt; decorator is the way that the code flags to Visual Studio that this is a test.&lt;/p&gt;

&lt;p&gt;I can now check my test is running from the test explorer.  You can get this window by going to &lt;code&gt;View &amp;gt; Test Explorer&lt;/code&gt;.  Then you just need to press the green button at the top to run a test:&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%2Fx8dhxfucghmjjzfaj10j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx8dhxfucghmjjzfaj10j.png" alt="Test explorer" width="359" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We're now on to the point that can be a little difficult for developers as it requires a different mindset to standard development.  We need to work out how to &lt;em&gt;break&lt;/em&gt; our code and make sure it can handle those cases.  The first one I can think of, is what happens if somebody has a &lt;em&gt;negative&lt;/em&gt; account value?  Well, I'd probably ask a question in Teams to see what other people thought, and in this case the code needs to throw an error.&lt;/p&gt;

&lt;p&gt;So I'll go back to modify the &lt;code&gt;CalculateInterest&lt;/code&gt; method so that it throws an error on negative balance:&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="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;CalculateInterest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IAccount&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"you can't calculate negative balances"&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="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;_standardRateCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StandardRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can now write a test for this.  As this is a test &lt;em&gt;related&lt;/em&gt; to the previous one I've worked on, I'll copy the last test and modify it to work for me:&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&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;void&lt;/span&gt; &lt;span class="nf"&gt;CalculateInterest_ThrowsAnArgumentException_WhenCalledWithNegativeBalance&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Arrange&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;account&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;Account&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Age&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="n"&gt;Balance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1000&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;interestCalculator&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;InterestCalculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_rateCalculator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Act and Assert&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Throws&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;interestCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateInterest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&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;As you can see, I've combined the &lt;code&gt;Act&lt;/code&gt; and &lt;code&gt;Assert&lt;/code&gt; sections in the test.  This is because the method throws an exception, which would stop the code execution in normal circumstances.  Therefore, the testing framework needs to catch the error, and we can do this with &lt;code&gt;Assert.Throws&lt;/code&gt;, but this does mean we can't separate the &lt;code&gt;Act&lt;/code&gt; and &lt;code&gt;Assert&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The next test to talk about, is that if this was a bank, we would have multiple customers, who can hold multiple accounts that we need to calculate the interest for.  You &lt;em&gt;could&lt;/em&gt; manually construct these objects if you want, but I'm going to use a package called &lt;a href="https://github.com/AutoFixture/AutoFixture" rel="noopener noreferrer"&gt;AutoFixture&lt;/a&gt; to make life easier.&lt;/p&gt;

&lt;p&gt;How AutoFixture works is that it takes in an object and fills out every field with dummy data.  The big benefit of this, is that if your object changes in the future, you don't need to modify all your tests to take in the new values, AutoFixture will automatically fill them out for you.  You can get AutoFixture in the same way you added NSubstitute to the project.&lt;/p&gt;

&lt;p&gt;I can then use AutoFixture in the following manner:&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&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;void&lt;/span&gt; &lt;span class="nf"&gt;CalculateInterest_CalculatesInterestInUnderTenSeconds_WhenCalledWithTenCustomersWithTenAccountsEach&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Arrange&lt;/span&gt;
    &lt;span class="n"&gt;Fixture&lt;/span&gt; &lt;span class="n"&gt;fixture&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;Fixture&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// This allows us to use fixtures on interfaces, by telling AutoFixture to use a concrete implementation&lt;/span&gt;
    &lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Customizations&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="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TypeRelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IAccount&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&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;customers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateMany&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;foreach&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;customer&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Accounts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateMany&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAccount&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;interestCalculator&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;InterestCalculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_rateCalculator&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// setting up a stopwatch to check how long this will take&lt;/span&gt;
    &lt;span class="n"&gt;Stopwatch&lt;/span&gt; &lt;span class="n"&gt;stopwatch&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;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// the maximum time we're giving this method to run&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;maxTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;TotalMilliseconds&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Act&lt;/span&gt;
    &lt;span class="n"&gt;stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;foreach&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;customer&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&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;account&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Accounts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;interestCalculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateInterest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&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="n"&gt;stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Assert&lt;/span&gt;
    &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ElapsedMilliseconds&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;maxTime&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;This is a fairly advanced implementation of using AutoFixture as I'm using an interface, and the test isn't great as it's pretty slow, but I hope it helps show how powerful it can be when you just need some fake data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other things of note
&lt;/h2&gt;

&lt;p&gt;You might have noticed when you were looking in NuGet, there was a package called &lt;a href="https://github.com/coverlet-coverage/coverlet" rel="noopener noreferrer"&gt;coverlet&lt;/a&gt; installed into the project:&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%2Fkfii81v1daizpzebh5jx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfii81v1daizpzebh5jx.png" alt="Coverlet" width="800" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a really great tool we can use to implement a paid feature into the community edition of Visual Studio, which is the ability to get test coverage.  It's also the package I use when building CICD pipelines in order to show test coverage in ADO and SonarQube together.&lt;/p&gt;

&lt;p&gt;To use this locally, you can run coverlet by going to the package manager console and running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--collect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"XPlat Code Coverage"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to view the results in a human-readable format, you will need to install the NuGet package &lt;a href="https://github.com/danielpalme/ReportGenerator" rel="noopener noreferrer"&gt;Report Generator&lt;/a&gt;.  Unfortunately, this isn't quite as easy as just installing the NuGet package.&lt;/p&gt;

&lt;p&gt;The easiest thing to do is find where the coverage report is generated to, which in my case was a folder called &lt;code&gt;TestResults&lt;/code&gt; which was at the top level of test application:&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%2F7fylb5a7rswnfv9a3l4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7fylb5a7rswnfv9a3l4t.png" alt="Test results" width="796" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You then need to run the following 4 commands from within that folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dotnet-reportgenerator-globaltool&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dotnet-reportgenerator-globaltool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--tool-path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tool-manifest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dotnet-reportgenerator-globaltool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you've done this, you should see a &lt;code&gt;.config&lt;/code&gt; folder containing a JSON file and a &lt;code&gt;tools&lt;/code&gt; folder which has a copy of report generator in it.  This indicates you're ready to run report generator with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reportgenerator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-reports:.\coverage.cobertura.xml"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-targetdir:.\report"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a folder generated called &lt;code&gt;report&lt;/code&gt; that contains a number of files.  All you need to do to see the report now, is find the file ending in .html and opening it:&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%2Fhkq8jn8jr2atrvmfuort.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkq8jn8jr2atrvmfuort.png" alt="Code coverage" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How to setup a simple static website using Svelte (with login)</title>
      <dc:creator>Jack Lewis</dc:creator>
      <pubDate>Fri, 19 May 2023 11:57:51 +0000</pubDate>
      <link>https://dev.to/jlewis92/how-to-setup-a-simple-static-website-using-svelte-with-login-4e0</link>
      <guid>https://dev.to/jlewis92/how-to-setup-a-simple-static-website-using-svelte-with-login-4e0</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://svelte.dev/" rel="noopener noreferrer"&gt;Svelte&lt;/a&gt; is an up and coming JavaScript framework being developed by Rich Harris (and the Svelte team).  It's gained acclaim for being very simple to use, reducing boilerplate and generally being a nice platform to develop new web apps on.  This article is an attempt to explain how you can leverage the power of Svelte to create modern static sites that can be hosted from simple file storage like S3 in AWS.&lt;/p&gt;

&lt;p&gt;While hosting the application is outside the scope of this article, I do have another &lt;a href="https://dev.to/jlewis92/the-aws-cloud-resume-challenge-4p17"&gt;here&lt;/a&gt; which explains how to do the hosting within it.&lt;/p&gt;

&lt;p&gt;It needs to be understood, that converting Svelte to run in a static context will; mean that a lot of the features touted by the Svelte team will be disabled.  However, I still feel it's worth it, as writing in Svelte is still a really nice experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;p&gt;The main benefit of this approach is cheap hosting.  You don't need to run a docker container or a machine in your cloud service provider of choice, which reduces the cost of the website as you're only paying for serving files.  Additionally, many cloud service providers have products that allow you to leverage a &lt;a href="https://www.cloudflare.com/learning/cdn/what-is-a-cdn/" rel="noopener noreferrer"&gt;content delivery network&lt;/a&gt; easily.  Finally, static applications are &lt;em&gt;fast&lt;/em&gt; because there's no need communicate backwards and forwards with a server, it just downloads some files to the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawbacks
&lt;/h2&gt;

&lt;p&gt;Essentially, all server based code in a Svelte will not work using static hosting.  Given Svelte is split into code that runs everywhere and code that runs in the server, this can be a little annoying.  You'll need to remove all &lt;code&gt;.ts&lt;/code&gt;/&lt;code&gt;.js&lt;/code&gt; files that contain the &lt;code&gt;server&lt;/code&gt; keyword (i.e.: &lt;code&gt;+page.server.ts&lt;/code&gt; or &lt;code&gt;+server.js&lt;/code&gt;) as these files won't work.  Additionally, some features ofSvelte won't work such as &lt;a href="https://joyofcode.xyz/sveltekit-routing#dynamic-routes" rel="noopener noreferrer"&gt;dynamic routing&lt;/a&gt; and &lt;a href="https://learn.svelte.dev/tutorial/ssr" rel="noopener noreferrer"&gt;server side rendering&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tutorial
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;You'll need an up-to-date version of your favourite node based package manager.  I'm using npm, but pnpm and yarn both work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;to start with we need to create aSvelte app from the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm createSvelte@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will ask you a few questions about how you want to set up your project.  For the cases of this demo, I went with a skeleton project and with TypeScript.  You select options (such as prettier) using the space bar.&lt;/p&gt;

&lt;p&gt;You then need to run the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just for context, you can run &lt;code&gt;npm build&lt;/code&gt; at this stage to see what the built output looks like without convertingSvelte to run as static. You can see it just generates a large number of files in the &lt;code&gt;.svelte-kit&lt;/code&gt; folder.  You can run from this directory using &lt;code&gt;npm run preview&lt;/code&gt;:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Converting to static
&lt;/h3&gt;

&lt;p&gt;It's really quite straight forward to convert aSvelte app to run in a static context.  Svelte does this using &lt;a href="https://kit.svelte.dev/docs/adapters" rel="noopener noreferrer"&gt;adapters&lt;/a&gt;.  By default, Svelte uses the &lt;code&gt;auto&lt;/code&gt; adapter which works best for general cases. There are a few different adapters, and you choose which one based on what you're doing.  For example, there are adapters based on providers (such as Netlify or Vercel) however, the one we're interested in, is &lt;code&gt;adapter-static&lt;/code&gt; which makes Svelte run like a static site generator.&lt;/p&gt;

&lt;p&gt;In order to get this to work, all you need to do is to install the &lt;code&gt;adapter-static&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -D @sveltejs/adapter-static
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then go to &lt;code&gt;svelte.config.js&lt;/code&gt; at the top level, and change the line talking about &lt;code&gt;adapter-auto&lt;/code&gt; to say &lt;code&gt;adapter-static&lt;/code&gt;.  I.e.:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sveltejs/adapter-auto&lt;/span&gt;&lt;span class="dl"&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&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@sveltejs/adapter-static&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; there's a lot of additional settings you can set for this, but for the purposes of this guide, we're not going change any of the defaults.&lt;/p&gt;

&lt;p&gt;At this point you can try and build the application, and it will instantly spit out a warning about dynamic routes in the &lt;code&gt;src/routes&lt;/code&gt; folder.  It gives you a bunch of options on how to fix this, but the simplest is to create a file called &lt;code&gt;layout.{ts|js}&lt;/code&gt; in &lt;code&gt;src/routes&lt;/code&gt; and then place the following into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prerender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This forces all files in the &lt;code&gt;src/routes&lt;/code&gt; folder to be prerendered, which is what we need for a static site.&lt;/p&gt;

&lt;p&gt;At this point, if you run &lt;code&gt;npm run build&lt;/code&gt; you should see a &lt;code&gt;build&lt;/code&gt; folder created that contains an &lt;code&gt;index.html&lt;/code&gt;, which is the entry point for the static site:&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%2Fzpezsosd7gpd08q6c1wa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpezsosd7gpd08q6c1wa.png" alt="static generated" width="345" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  UI library
&lt;/h3&gt;

&lt;p&gt;Usually, one of the first things I do on creating a new web app is to throw a UI library in to help style components.  There are &lt;a href="https://joyofcode.xyz/svelte-ui-libraries" rel="noopener noreferrer"&gt;several&lt;/a&gt; UI libraries that can be used by Svelte, but in this case I went with &lt;a href="https://daisyui.com/" rel="noopener noreferrer"&gt;daisyUI&lt;/a&gt; because it's a fairly popular UI library which includes &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;tailwind&lt;/a&gt;.  To install daisyUI, you first need to install tailwind.  There's a few different ways to do this (such as &lt;a href="https://tailwindcss.com/docs/guides/sveltekit" rel="noopener noreferrer"&gt;this&lt;/a&gt; guide), but the easiest way I've found is the following command, which also adds &lt;a href="https://postcss.org/" rel="noopener noreferrer"&gt;PostCSS&lt;/a&gt; and &lt;a href="https://autoprefixer.github.io/" rel="noopener noreferrer"&gt;AutoPrefixer&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;npxSvelte-add@latest tailwindcss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should then be able to install daisyUI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install daisyui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final step for daisyUI is to then add a plugin for daisyUI into the &lt;code&gt;tailwind.config.cjs&lt;/code&gt; folder.  It should look something like this:&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="cm"&gt;/** @type {import('tailwindcss').Config}*/&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/**/*.{html,js,svelte,ts}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;

    &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;extend&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="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;daisyui&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;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can tell that daisyUI is installed correctly in 2 ways, the first is to add a styled component, like a button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Button&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fythy98hueyy2al5a0frj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fythy98hueyy2al5a0frj.png" alt="styled button" width="454" height="134"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;or alternatively, you can run &lt;code&gt;npm run build&lt;/code&gt; or &lt;code&gt;npm run dev&lt;/code&gt; and the output tells you that daisyUI is installed:&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%2Ff17zwgzt0dlsenbfbzgd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff17zwgzt0dlsenbfbzgd.png" alt="daisy UI" width="762" height="155"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Animations
&lt;/h3&gt;

&lt;p&gt;While Svelte has a pretty good built-in library of animations, if I do need some more custom animations, I don't actually use a library, but I do use &lt;a href="https://animista.net/" rel="noopener noreferrer"&gt;Animista&lt;/a&gt; to pick out something which works for me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repository Setup
&lt;/h3&gt;

&lt;p&gt;I talk about this in more detail in my article &lt;a href="https://dev.to/jlewis92/my-setup-for-publishing-to-devto-using-github-1k0n"&gt;here&lt;/a&gt;, but I'm a big fan of adding &lt;a href="https://pre-commit.com/" rel="noopener noreferrer"&gt;pre-commit&lt;/a&gt; into my git repositories to help out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Login
&lt;/h3&gt;

&lt;p&gt;Whether it's a good idea or not, it's definitely possible to add login functionality to a static site.  This can be done through JSON web tokens and the OAuth &lt;a href="https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow" rel="noopener noreferrer"&gt;Authorization code flow&lt;/a&gt;.  How this works is that we offload authorization to a third party, that then returns a token that verifies a user is logged.  That token can then be used to authenticate with an API somewhere.  This can be a little difficult to get your head around, but this essentially what happens when you click the "log in with Google/Facebook/GitHub" button on a website and is known as "Single Sign On" or "SSO".&lt;/p&gt;

&lt;p&gt;We're specifically using the authorization code flow as this is the one needed for when the entire website is served to the browser i.e. when using a static website.&lt;/p&gt;

&lt;p&gt;If you &lt;em&gt;do&lt;/em&gt; want to add login functionality to a static website, you need to make sure that all data related to a user comes in from an authenticated backing API.  For example, API gateway in AWS.  The reason for this is that any data you serve to a website can be modified by the client, meaning that somebody could "force" a login by just changing the JavaScript locally. You need to stop this by making something like an OAuth token required on every call to the backend, as this is verifying that a user has access to those resources.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why?
&lt;/h4&gt;

&lt;p&gt;This can be a little counterintuitive to do as if you're doing this, why don't you just use server side features of Svelte and avoid a separate API all together, which would likely be faster than offloading the calls to the browser.  The answer to this is mainly in cases where you have a public API being used by others, as well as yourself or that you're using the same resources with multiple OAuth flows, such as the &lt;a href="https://curity.io/resources/learn/oauth-client-credentials-flow/" rel="noopener noreferrer"&gt;client credential flow&lt;/a&gt; for machine to machine calls.  As such, this can be useful on admin portals for application support.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to add
&lt;/h4&gt;

&lt;p&gt;Just about every cloud provider has their own implementation of a JavaScript library that can offload the complexity of writing your own implementation.  For example, AWS has &lt;a href="https://aws.amazon.com/amplify/" rel="noopener noreferrer"&gt;Amplify&lt;/a&gt;, GCP has &lt;a href="https://firebase.google.com/docs/auth/web/firebaseui" rel="noopener noreferrer"&gt;Firebase&lt;/a&gt; and Azure has &lt;a href="https://learn.microsoft.com/en-us/javascript/api/overview/azure/identity-readme?view=azure-node-latest" rel="noopener noreferrer"&gt;Identity&lt;/a&gt;.  You can also use a more generic library like &lt;a href="https://authjs.dev/reference/sveltekit" rel="noopener noreferrer"&gt;Auth.js&lt;/a&gt;. However, I'm going to go with &lt;a href="https://www.Auth0.com/" rel="noopener noreferrer"&gt;Auth0&lt;/a&gt; in this guide as I've not used this service before, and it's a slightly more "neutral" option than going for a service offered directly by a cloud provider.  Additionally, Auth0 will allow you to get started without needing to add a credit card, which makes it useful for demonstrations.&lt;/p&gt;

&lt;p&gt;Picking the best service for your needs is outside the scope of this article, but safe to say a lot of considerations to keep in mind.  This can be everything from a cloud provider offering a tighter integration with their own products to the number of monthly free users you're allowed (called MAU's) to the number of SSO &lt;a href="https://en.wikipedia.org/wiki/List_of_OAuth_providers" rel="noopener noreferrer"&gt;providers&lt;/a&gt; offered for integration.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup
&lt;/h4&gt;

&lt;p&gt;To start with you need to sign up for an account with Auth0 and add the Auth0 single page app SDK to your Svelte application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install @auth0/auth0-spa-js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this, you need to go into the front page of Auth0, and click "create application" and select "Single Page Application":&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%2Ff6n7pmomy3kc1r3dcq5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6n7pmomy3kc1r3dcq5z.png" alt="Auth0 spa" width="800" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a large number settings you can use, but that's outside this guide.  However, a nice feature of the documentation is that if you're logged in, the documentation will be updated to use details for your account:&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%2Ft7079ec1f4bzgi9d5sib.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7079ec1f4bzgi9d5sib.png" alt="Auth0 documentation" width="800" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You then need to set callback and logout URL's. Svelte usually sets the startup port as 5173, but pick whatever you need:&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%2Frxjm7ymeh7zsncs6zvze.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frxjm7ymeh7zsncs6zvze.png" alt="Auth0 callback" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've put localhost in at the moment as I'm just running this locally.  Obviously if you're actually running this, just set the callback and logout URL's to the valid paths there.&lt;/p&gt;

&lt;p&gt;At this point all you need to do to set up the login functionality is to construct the auth0 object:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth0Client&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@auth0/auth0-spa-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Auth0Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;domain&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;client id&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then you can start the login process by setting up a button which calls Auth0:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;login&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="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loginWithRedirect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;authorizationParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:5173/&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;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;btn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Login&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you press the button, it should take you to the Auth0 login page where you can now sign up a user.  It also handles email verification and SSO.  Once you do this, it should redirect you back to your application.  You can tell it's working at this point if you can see that you have token in the URL as a query parameter.  For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:5173/?code=tlcjskafaUOODPicz_P83g6YrwgiFxOO7PXv0gNZAKDsC&amp;amp;state=ekdCQzFRZjllTmJyOTUwcU5XWjNqV0RQN04wZC11alZ0UHB1Q0hPNElYTQ%3D%3D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to handle this, we need to call the &lt;code&gt;auth0.handleRedirectCallback()&lt;/code&gt; function on page load.  This can be done using &lt;code&gt;onMount&lt;/code&gt; in Auth0:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;onMount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleRedirectCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&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;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&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="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not authenticated&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;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Logout functionality is just as straight forward:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;logout&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="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logout&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;For context, here's a full minimal implementation of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight svelte"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Auth0Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@auth0/auth0-spa-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;onMount&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;svelte&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Auth0Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;auth0 domain&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;auth0 client id&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;login&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="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loginWithRedirect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;authorizationParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;redirect_uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://localhost:5173/&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;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;logout&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="nx"&gt;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logout&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;onMount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleRedirectCallback&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;user&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;auth0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUser&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="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not authenticated&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;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Login&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;on:click=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;logout&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Logout&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;#if&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;hello &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nickname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;/if&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final thing to talk about, is that if you want to get the token to use in an authorization header for retrieving data through an API, you can get this using either &lt;code&gt;auth0.getTokenSilently()&lt;/code&gt; or &lt;code&gt;auth0.getTokenWithPopup()&lt;/code&gt; depending on your needs.&lt;/p&gt;

&lt;p&gt;While it's outside the scope of this article, Auth0 provides documentation about how to integrate with all the major cloud providers.  For example, the documentation for how to implement Auth0 with AWS API Gateway is found &lt;a href="https://auth0.com/docs/customize/integrations/aws/aws-api-gateway-custom-authorizers" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>svelte</category>
      <category>auth0</category>
      <category>typescript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A quick tour of collections in C#</title>
      <dc:creator>Jack Lewis</dc:creator>
      <pubDate>Fri, 12 May 2023 19:32:25 +0000</pubDate>
      <link>https://dev.to/jlewis92/a-quick-tour-of-collections-in-c-81d</link>
      <guid>https://dev.to/jlewis92/a-quick-tour-of-collections-in-c-81d</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;Collections in C# are really quite useful when used correctly, but they tend to be used far less often than lists despite fulfilling a similar purpose.  There are a lot of versions of a collection in C# and this article aims to display why you should use collections in your code, as well as how to use them. It's also important to understand that by &lt;em&gt;Collection&lt;/em&gt; I mean, a specific type of object, rather than the System.Collection namespace or a "collection" which can include essentially anything that can be described as "a collection of objects".  I've also included a few of the less common objects in C# that are called a &lt;code&gt;collection&lt;/code&gt; in the name of the class.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a collection?
&lt;/h2&gt;

&lt;p&gt;It's really quite straightforward, in that a collection is a list of things and most of them implement IList.  However, there are some nuances in this.  For example, methods in a collection are marked as virtual, meaning they can be overridden for custom implementations.  What this means is that additional functionality (or custom handling) is much easier to add to a collection over a list.  On the other hand, lists have more functionality related to sorting and searching for data.  What this all adds up to, is that if you think you may require a custom implementation of a list (for example, to fire events when items are added), use a collection.  In fact, it's generally recommended providing &lt;em&gt;collections&lt;/em&gt; to users rather than lists, but in reality this rarely happens as lists work in 99.99% of use cases.  Also, in my experience it's far more likely to need sort and search functionality than any other operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of collection
&lt;/h2&gt;

&lt;p&gt;Once again, I'm going to go through some collection types in C# as well as the differences between them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Collection
&lt;/h3&gt;

&lt;p&gt;This is the "default" class for a collection. MSDN does &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.collection-1?view=net-7.0" rel="noopener noreferrer"&gt;describe this as a "base class"&lt;/a&gt; but it's not an abstract class and should not be confused for &lt;code&gt;CollectionBase&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This collection can be declared in a very similar fashion to a list:&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;Collection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myCollection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&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;Adding, removing and inserting data in a collection is quite straightforward:&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;myCollection&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="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Insert&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="s"&gt;"item 2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveAt&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="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While finding data in a collection isn't as extensive as a list, there's still a lot of ways you can find data in a collection:&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;// index based&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;itemOne&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;itemTwo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ElementAt&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="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;firstTwo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;lastTwo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TakeLast&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="c1"&gt;// item based&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;anotherItemTwo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"item 2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;nullItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"not in the list"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;amotherMultipleitems&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TakeWhile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;anotherItemList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Intersect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"item 2"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// returning indexes&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;span class="c1"&gt;// boolean based&lt;/span&gt;
&lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;isEqualTo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myCollection&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="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"item 2"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While there aren't any methods to &lt;em&gt;sort&lt;/em&gt; the collection, there are functions that can be used to order the collection:&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;IOrderedEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;orderedEnumerable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderByDescending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"item 2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// item 2, item 1&lt;/span&gt;
&lt;span class="n"&gt;IOrderedEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;orderedEnumerableTwo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OrderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// item 2, item 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, collections provide a lot of functions to cast to other object types.  For example, the classic &lt;code&gt;ToList()&lt;/code&gt;,&lt;code&gt;ToString()&lt;/code&gt; and &lt;code&gt;ToArray()&lt;/code&gt; but also to various specialized objects, such as &lt;code&gt;ToSortedSet()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Collection class lives in the &lt;code&gt;System.Collections.ObjectModel&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keyed Collection
&lt;/h3&gt;

&lt;p&gt;Frankly a keyed collection should probably be discussed in my &lt;a href="https://dev.to/jlewis92/a-quick-tour-of-dictionaries-in-c-gg9"&gt;article about dictionaries&lt;/a&gt; as they are pretty closely related.  There are some differences though.  For example, a keyed collection guarantees &lt;em&gt;order&lt;/em&gt; whereas a Dictionary does not.  Additionally, a keyed collection is actually an abstract class, meaning that it needs to be inherited from to work:&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;// This is a pretty useless class as it just sets the key to the value.&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;ConcreteKeyedCollection&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;KeyedCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&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;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetKeyForItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;item&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="n"&gt;item&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;Once this done, you can declare a keyed list like this:&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;KeyedCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myKeyedCollection&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;ConcreteKeyedCollection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While I did say this acts like a dictionary internally, you still add items without a key and then this key is set based on the data in the abstract 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="n"&gt;myKeyedCollection&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="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's interesting about the keyed collection, is that it contains both a collection &lt;em&gt;and&lt;/em&gt; a dictionary.  This means you have access to data retrieval in both the fashion listed above and methods found on dictionaries, like &lt;code&gt;TryGetValue()&lt;/code&gt;.  This can be seen when we take a look at the QuickWatch window:&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%2F9pf6jakmy5425gpkelj1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9pf6jakmy5425gpkelj1.png" alt="Keyed collections" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Obviously, this does mean you get a slight performance hit compared to just using a straight dictionary (as you're maintaining a list as well), there is also the increased code complexity due to having to create concrete implementation of the class. Therefore, unless you specifically &lt;em&gt;need&lt;/em&gt; an ordered dictionary, I would just stick with an ordinary dictionary.&lt;/p&gt;

&lt;p&gt;The keyed collection abstract class lives in the &lt;code&gt;System.Collections.ObjectModel&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observable Collection
&lt;/h3&gt;

&lt;p&gt;While the collection might be the "default" class, the one I most often see used, is the observable collection. This is especially true within C# GUI programs such as WPF.  The reason for this, is that when items are added to this type of collection, an event is fired.  This makes it extremely useful for two-way data binding as compared to a list, which does not tell an observer when the collection has changed.&lt;/p&gt;

&lt;p&gt;Observable collections can be declared like this:&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;ObservableCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myObservableCollection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ObservableCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&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;As I mentioned above, the main difference between a standard collection and an observable collection, is an event called &lt;code&gt;CollectionChanged&lt;/code&gt; which can be used in the same way as a standard event:&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;SomeMethod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;myObservableCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CollectionChanged&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;OnCollectionChanged&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;void&lt;/span&gt; &lt;span class="nf"&gt;OnCollectionChanged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NotifyCollectionChangedEventArgs&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;NotImplementedException&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 observable collection class can also be found in the &lt;code&gt;System.Collections.ObjectModel&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read Only Collection
&lt;/h3&gt;

&lt;p&gt;As in my articles on dictionaries and lists, a read only collection is used when you don't want a user to be able to modify a collection after initialization.  Unlike lists and dictionaries though, a read-only collection is &lt;em&gt;not&lt;/em&gt; an interface and is instead a concrete implementation:&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myStringList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"item 2"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;ReadOnlyCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myReadOnlyCollection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ReadOnlyCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;myStringList&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the read-only collection class can also be found in the &lt;code&gt;System.Collections.ObjectModel&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blocking Collection
&lt;/h3&gt;

&lt;p&gt;Within collections, this is the thread-safe version.  It's essentially a C# implementation of the &lt;a href="https://jenkov.com/tutorials/java-concurrency/producer-consumer.html" rel="noopener noreferrer"&gt;producer-consumer pattern&lt;/a&gt;.  In addition to blocking, there is also bounding functionality, where you can set the maximum size of the collection:&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;BlockingCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myBlockingCollection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BlockingCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// only allows 2 items before being blocked&lt;/span&gt;
&lt;span class="n"&gt;BlockingCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myBoundedBlockingCollection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;BlockingCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can add and remove items in a few different ways that are different from standard:&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;myBoundedBlockingCollection&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="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;myBoundedBlockingCollection&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="s"&gt;"item 2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;myBoundedBlockingCollection&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="s"&gt;"item 3"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// this will block the thread until an item is removed&lt;/span&gt;
&lt;span class="n"&gt;myBoundedBlockingCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"item 3"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// this continues execution even if bounded&lt;/span&gt;

&lt;span class="c1"&gt;// removing items&lt;/span&gt;
&lt;span class="n"&gt;myBoundedBlockingCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// this is FIFO and has several overloads&lt;/span&gt;
&lt;span class="n"&gt;myBoundedBlockingCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;myBoundedBlockingCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// blocks the thread until something added&lt;/span&gt;
&lt;span class="n"&gt;myBoundedBlockingCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryTake&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&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="c1"&gt;// the second parameter is a blocking timeout for if the collection is empty&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most notably, addition/removal operations accept cancellation tokens for additional use under threaded workloads.&lt;/p&gt;

&lt;p&gt;In addition to this, blocking collections provide a few additional properties:&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;int&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myBoundedBlockingCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BoundedCapacity&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// this would be 2&lt;/span&gt;

&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;addingCompleted&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myBoundedBlockingCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAddingCompleted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;span class="n"&gt;myBoundedBlockingCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CompleteAdding&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;addingCompletedAfterCompleteAdding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myBoundedBlockingCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAddingCompleted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;fullyCompleted&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myBoundedBlockingCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCompleted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// only true when `CompleteAdding` has been called and the collection is empty&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, if you're working with &lt;em&gt;multiple&lt;/em&gt; blocking collections in multithreaded workflow, the class itself has some static methods that can be helpful:&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlockingCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myBlockingCollectionList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BlockingCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;myBlockingCollection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;myBoundedBlockingCollection&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// just picks one of the blocking collections to add/remove the item&lt;/span&gt;
&lt;span class="n"&gt;BlockingCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddToAny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myBlockingCollectionList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"an item"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BlockingCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="nf"&gt;TakeFromAny&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myBlockingCollectionList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// there are also `Try` versions of both these methods&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The blocking collection lives in the &lt;code&gt;System.Collections.Concurrent&lt;/code&gt; namespace&lt;/p&gt;

&lt;h3&gt;
  
  
  Name Value Collection
&lt;/h3&gt;

&lt;p&gt;The name value collection doesn't work the same way as the other collections in this article and is more like a combination of a dictionary, collection and tuple.  It's primarily used for holding things like headers for web requests.  Additionally, a name value collection only accepts strings.&lt;/p&gt;

&lt;p&gt;It can be declared like the following:&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;NameValueCollection&lt;/span&gt; &lt;span class="n"&gt;myNameValueCollection&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;NameValueCollection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moreover, what makes the name value collection different from a dictionary, is that the keys do not have to be unique.  The best way I've found to think about this, is that while addition works by adding a single string to both the key and value, internally it adds the value to an array:&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;myNameValueCollection&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="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;myNameValueCollection&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="s"&gt;"item 2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"item 2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;myNameValueCollection&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="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"item 3"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]?&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myNameValueCollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// contains "item 1" and "item 3"&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;commaSeparated&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myNameValueCollection&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"item 1"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// interestingly, this returns a comma separated list of values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The name value collection lives in the &lt;code&gt;System.Collections.Specialized&lt;/code&gt; namespace&lt;/p&gt;

&lt;h3&gt;
  
  
  Visual Basic Collection
&lt;/h3&gt;

&lt;p&gt;Generally I'd consider this collection a forerunner to the collection and dictionary in C# that has been carried forward (I'm pretty sure it came from Visual Basic). It can be constructed like the following, of which I've included the namespace to avoid confusion with the collection referenced above:&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;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VisualBasic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Collection&lt;/span&gt; &lt;span class="n"&gt;myVisualBasicCollection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Microsoft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VisualBasic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Collection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wouldn't recommend this collection in &lt;del&gt;modern&lt;/del&gt; C# at all, because it's a holdover from Visual Basic, and it's not generic, but instead uses &lt;code&gt;object&lt;/code&gt;.  This means it's not type-safe.  For example this is perfectly valid:&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;myVisualBasicCollection&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="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;myVisualBasicCollection&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="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, this is technically a key-value collection where you can choose not to add a key.  This is another reason I would not to use this, but you can add a key in the following way:&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;myVisualBasicCollection&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="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given this is a much older class, there is also a lot fewer methods that can be used to interact with objects held within one.&lt;/p&gt;

&lt;p&gt;As mentioned above, this collection can be found in the &lt;code&gt;Microsoft.VisualBasic&lt;/code&gt; namespace.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>collections</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Turning Git into a large file backup solution</title>
      <dc:creator>Jack Lewis</dc:creator>
      <pubDate>Fri, 05 May 2023 19:21:00 +0000</pubDate>
      <link>https://dev.to/jlewis92/turning-git-into-a-large-file-backup-solution-10d1</link>
      <guid>https://dev.to/jlewis92/turning-git-into-a-large-file-backup-solution-10d1</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;NOTE:&lt;/em&gt;&lt;/strong&gt; this code, while working, is very much proof of concept and as such has lots of bugs.  I'm also not planning to fix these as this is a pretty stupid idea. However, I thought it was interesting enough to share.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;This whole thing came about from a few realizations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub allows a maximum of 100MB per file before they block uploads&lt;/li&gt;
&lt;li&gt;Based on &lt;a href="https://stackoverflow.com/questions/38768454/repository-size-limits-for-github-com" rel="noopener noreferrer"&gt;this&lt;/a&gt; answer from Stack Overflow, the "hard limit" for the size of a repository in GitHub is 100GB, but really should be kept under 5GB (and preferably 1GB) - it's a little difficult to find information on this though&lt;/li&gt;
&lt;li&gt;GitHub lets you have an unlimited number of repositories&lt;/li&gt;
&lt;li&gt;There's an endpoint that lets you set up a GitHub repository programmatically&lt;/li&gt;
&lt;li&gt;zip and tar files both have the ability to easily be split apart&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, pretty much from all of this, can I build something that can I take in a large file, split it apart into manageable chunks, upload it via Git and then reassemble everything when I need to restore from backup?&lt;/p&gt;

&lt;h3&gt;
  
  
  You talk about GitHub, why are you using Git?
&lt;/h3&gt;

&lt;p&gt;This comes down to the simple premise that GitHub would probably not be too happy with me trying this, so better not to do it at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR - yes I can
&lt;/h2&gt;

&lt;p&gt;Repository here:&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/jlewis92" rel="noopener noreferrer"&gt;
        jlewis92
      &lt;/a&gt; / &lt;a href="https://github.com/jlewis92/gitBackup" rel="noopener noreferrer"&gt;
        gitBackup
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &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;gitBackup&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;GitBackup contains the following commands:&lt;/p&gt;
&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;-&lt;/span&gt;r&lt;span class="pl-k"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;--&lt;/span&gt;restore    Restore files.

&lt;span class="pl-k"&gt;-&lt;/span&gt;b&lt;span class="pl-k"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;--&lt;/span&gt;backup     Backup files.

&lt;span class="pl-k"&gt;--&lt;/span&gt;help           Display this help screen.

&lt;span class="pl-k"&gt;--&lt;/span&gt;version        Display version information.&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;In addition to the command line arguments, there is an appSettings.json that can be set in the executable directory to further modify how the program works.  This is what this file looks like with all the defaults:&lt;/p&gt;
&lt;div class="highlight highlight-source-json notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;{
  &lt;span class="pl-ent"&gt;"AppSettings"&lt;/span&gt;: {
    &lt;span class="pl-ent"&gt;"FilesToBackupLocation"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&amp;lt;current folder&amp;gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
    &lt;span class="pl-ent"&gt;"BackupLocation"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&amp;lt;current folder&amp;gt;&lt;span class="pl-cce"&gt;\\&lt;/span&gt;backup&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-ent"&gt;"RestoreLocation"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&amp;lt;current folder&amp;gt;&lt;span class="pl-cce"&gt;\\&lt;/span&gt;backup&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-ent"&gt;"FilesToRestoreLocation"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&amp;lt;current folder&amp;gt;&lt;span class="pl-cce"&gt;\\&lt;/span&gt;restoreTestFolder&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-ent"&gt;"RecursiveFileBackup"&lt;/span&gt;: &lt;span class="pl-c1"&gt;false&lt;/span&gt;,
    &lt;span class="pl-ent"&gt;"GitSettings"&lt;/span&gt;: {
      &lt;span class="pl-ent"&gt;"RepositoryNamingConvention"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;gitBackup-&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
      &lt;span class="pl-ent"&gt;"RepositorySizeInKilobytes"&lt;/span&gt;: &lt;span class="pl-c1"&gt;800000&lt;/span&gt;,
      &lt;span class="pl-ent"&gt;"GitRemoteLocation"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;, &lt;span class="pl-ii"&gt;// this value will need to be set at least&lt;/span&gt;
      &lt;span class="pl-ent"&gt;"OverwriteOnAdd"&lt;/span&gt;: &lt;span class="pl-c1"&gt;true&lt;/span&gt;,
      &lt;span class="pl-ent"&gt;"Username"&lt;/span&gt;: &lt;span class="pl-c1"&gt;null&lt;/span&gt;,
      &lt;span class="pl-ent"&gt;"Email"&lt;/span&gt;: &lt;span class="pl-c1"&gt;null&lt;/span&gt;,
      &lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jlewis92/gitBackup" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Video in action here:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/824184559" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;Honestly, it seemed like a fun challenge and that's pretty much it.  Really, this is a terrible idea to choose as a backup strategy.  It's incredibly slow (that video above is over 1 minute of waiting for files to transfer &lt;em&gt;locally&lt;/em&gt;) and if you know anything about Git, binary files (such as zip files) are &lt;em&gt;terrible&lt;/em&gt; thing use Git to store. This is because Git works by analysing differences in files and saving them.  If you're using binaries, this means that git has to store a stupid amount of extra data to make it work.  Additionally, because of how I wrote this software, you could have up to &lt;em&gt;4&lt;/em&gt; copies of the same data on disk at the same time, which is pretty bad. Finally, it needs to be said that relying on a company to not just delete all your files if you do actually do this, it's probably a bad idea to even attempt to store anything in this manner.&lt;/p&gt;

&lt;p&gt;I know this section is more why &lt;em&gt;not&lt;/em&gt; to do this, which is why I really have no excuse for doing this, other than to say I could.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tooling choice
&lt;/h2&gt;

&lt;p&gt;I'm primarily a C# developer, so I'm going to use C# and given I'm going to be interacting with the Git CLI, the easiest project to choose is a console application.  Additionally, I decided to go with zip over TAR as I'm using windows. I'll also need a database to store where the files live in GitHub and given I'm already storing everything in files, and need to move them around, a file based database was my best option, and from these SQLite before, so this was my tool of choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Flowcharts
&lt;/h2&gt;

&lt;p&gt;At this point I needed to visualize the flow of the application in my head.  I decided I needed 2 commands to start with, &lt;code&gt;backup&lt;/code&gt; and &lt;code&gt;restore&lt;/code&gt;.  Given there's a fair amount of complexity on how exactly each command will work, here are some flowcharts explaining it:&lt;/p&gt;

&lt;h3&gt;
  
  
  Backup
&lt;/h3&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/jlewis92/embed/eYPGZxQ?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Restore
&lt;/h3&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/jlewis92/embed/mdzBPNO?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Structure
&lt;/h2&gt;

&lt;p&gt;As I said, I needed a simple database to keep track of where everything lives within the repositories and because I'm going to want to back that file up, the best choice for me was to use SQLite.  I decided to go with 3 tables:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manifest Entries:&lt;/strong&gt; holds details related to each file.&lt;br&gt;
&lt;strong&gt;Repository Entries:&lt;/strong&gt; holds details related to each repository being tracked&lt;br&gt;
&lt;strong&gt;Compressed File Entries:&lt;/strong&gt; holds details related to compressed file, as well as the manifest entry and repository entry that is attached to the compressed file.&lt;/p&gt;

&lt;p&gt;This fits together something like this:&lt;/p&gt;



&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/jlewis92/embed/OJBOzpo?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Structure
&lt;/h2&gt;

&lt;p&gt;I depended heavily on interfaces to build up this code base.  It's probably simplest to just show off the class diagram for this code below.  If you're interested in exploring the code itself, it's linked above.&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%2F5sh8h0kf3ycvhc38k0s3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5sh8h0kf3ycvhc38k0s3.png" alt="gitBackup class diagram" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Packages
&lt;/h3&gt;

&lt;p&gt;Within the code itself, I made use of a few packages to make life easier on myself.  The main ones chosen are listed here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CommandLineParser

&lt;ul&gt;
&lt;li&gt;Creates better syntax structures for command line arguments, such as -b for --backup&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;DotNetZip

&lt;ul&gt;
&lt;li&gt;Popular library for working with zip files in .Net&lt;/li&gt;
&lt;li&gt;This library was chosen as it has support for splitting files&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;LibGit2Sharp

&lt;ul&gt;
&lt;li&gt;It's a library for working with Git in C#&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Entity Framework Core

&lt;ul&gt;
&lt;li&gt;Common C# ORM used to link objects in code to the SQLite database&lt;/li&gt;
&lt;li&gt;One of the nice things about this implementation, is that I can call EnsureCreated() to create a database if it doesn't already exist&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Serilog

&lt;ul&gt;
&lt;li&gt;Excellent logging tool&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;TestableIO.System.IO.Abstractions

&lt;ul&gt;
&lt;li&gt;This library creates abstract version of System.IO which makes them far more testable&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Challenges
&lt;/h3&gt;

&lt;p&gt;There were a few issues I did run into when building this code out - thought I'd throw these out there in case anybody runs into something similar&lt;/p&gt;

&lt;h4&gt;
  
  
  Working with filesystems
&lt;/h4&gt;

&lt;p&gt;I had to work &lt;em&gt;heavily&lt;/em&gt; with the file system on this project.  Everything from the zip library outputting directly into files, to moving things around for the git repositories.  While the code itself doesn't do anything fancy, System.IO is pretty untestable in C# and I wanted to write some tests for this.  As a result, I needed some way to test the file system.  I settled on using the TestableIO library mentioned above which allows you to use dependency injection on an interface class, rather than writing my own wrapper.&lt;/p&gt;

&lt;h4&gt;
  
  
  SQLite Write-Ahead Logging
&lt;/h4&gt;

&lt;p&gt;If you're not sure what write-ahead logging is, there's a full explanation &lt;a href="https://www.sqlite.org/wal.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.  Essentially, it's a way of implementing ATOMIC rollback in SQLite. However, how this works is that new commits to the database are held in a separate file, and then occasionally added back to the database in a process known as "checkpointing".  Unfortunately, because I'm moving the database file around shortly after writing, the additional time taken waiting for checkpointing to occur meant that I'd be uploading a version of the database that doesn't contain the latest changes.  While I did attempt to modify when the checkpointing occurred, I decided to avoid problems entirely and turned this feature off.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the code
&lt;/h3&gt;

&lt;p&gt;GitBackup contains the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--restore&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Restore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;-b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--backup&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="n"&gt;Backup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;--help&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="n"&gt;Display&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;screen.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Display&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;information.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to the command line arguments, there is an appSettings.json that can be set in the executable directory to further modify how the program works.  This is what this file looks like with all the defaults:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AppSettings"&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;"FilesToBackupLocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;current folder&amp;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;"BackupLocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;current folder&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;backup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"RestoreLocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;current folder&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;backup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"FilesToRestoreLocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;current folder&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;restoreTestFolder"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"RecursiveFileBackup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"GitSettings"&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;"RepositoryNamingConvention"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gitBackup-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"RepositorySizeInKilobytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;800000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"GitRemoteLocation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;need&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;least&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"OverwriteOnAdd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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;"FileCompressionSettings"&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;"FileSizeInKilobytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80000&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="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;
  
  
  In the future
&lt;/h2&gt;

&lt;p&gt;So now I've written this and established that it's pretty useless, I'm not likely to come back to this project.  However, if I was, there's a few improvements that could be made quickly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Additional logging

&lt;ul&gt;
&lt;li&gt;I've added some, but it pretty poor&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Better error handling

&lt;ul&gt;
&lt;li&gt;this was built quickly and as such error handling is pretty rudimentary&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;More tests

&lt;ul&gt;
&lt;li&gt;I've written a few, but I'd need more to write a fully robust test suite&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Multithreading

&lt;ul&gt;
&lt;li&gt;Granted the majority of the time this program executes is waiting for Git, but writing in the ability to pack/unpack zip files and upload git repositories in a multithreaded fashion would likely speed up the execution of this code quickly&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>git</category>
      <category>backup</category>
      <category>csharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>A quick tour of lists in C#</title>
      <dc:creator>Jack Lewis</dc:creator>
      <pubDate>Fri, 28 Apr 2023 20:29:47 +0000</pubDate>
      <link>https://dev.to/jlewis92/a-quick-tour-of-lists-in-c-2id5</link>
      <guid>https://dev.to/jlewis92/a-quick-tour-of-lists-in-c-2id5</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;Some of you might have seen my &lt;a href="https://dev.to/jlewis92/a-quick-tour-of-dictionaries-in-c-gg9"&gt;previous article&lt;/a&gt; around dictionaries in C#.  Seemed to be fairly popular, so I thought I'd write a similar around lists in C#.  AS in dictionaries, C# provides a lot of versions of a simple list.  I've not seen previous articles comparing and contrasting the different versions of a list and where to use them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's a list?
&lt;/h2&gt;

&lt;p&gt;Quite simply, a list is a data structure in C# used to hold list of objects of the same type.  They are also generic, meaning the type in the list is declared at initialization.  Lists are also specifically an &lt;em&gt;ordered&lt;/em&gt; data structure, meaning that the order you add items to the list is the order of the items in that list, including duplicate values.  Finally, lists are "designed" to be throwaway.  This is because it can be difficult to extend a list in the same way a collection can be. In fact, while collections are outside the scope of this article, it's generally considered better practice to return a collection from public methods, but in reality a list works fine 999 times out of 1000.&lt;/p&gt;

&lt;p&gt;There is more information on lists found &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1?view=net-8.0" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of list
&lt;/h2&gt;

&lt;p&gt;As in the previous article, I'll run through some of the features I use most often in the "default" list and move onto other implementations afterwards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I'm using nullable strings (i.e.: &lt;code&gt;string?&lt;/code&gt;) throughout this article to make it more clear.&lt;/p&gt;

&lt;h3&gt;
  
  
  List
&lt;/h3&gt;

&lt;p&gt;Lists can be declared like the following example using a list of strings:&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&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;In general, data can be added, inserted or removed from the list like the following:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://dotnetfiddle.net/Widget/8CuFMQ" width="100%" height="600"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;There are a &lt;em&gt;lot&lt;/em&gt; of methods that can be used to find things in lists.  Here's a few of the more common ones.&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;// The simplest way to find an item&lt;/span&gt;
&lt;span class="c1"&gt;// returns null if it's not in the list&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;foundString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"some string"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;nullString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"this is not in the list"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// If you want more than one, you can use .Where:&lt;/span&gt;
&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;multipleFound&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// As a list:&lt;/span&gt;
&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;multipleFoundAsList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Returns an empty list if it finds nothing&lt;/span&gt;
&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;noneFound&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"nothing here"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// There are also methods like select, which checks whether the item matches&lt;/span&gt;
&lt;span class="c1"&gt;// and then returns a bool based on the match.&lt;/span&gt;
&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"first string"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// you can find by index like this:&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;firstItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;//string throwsArgumentOutOfRangeException = stringList[4];&lt;/span&gt;

&lt;span class="c1"&gt;// You cans earch for first or last directly as well&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;anotherFirst&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// If you don't want it to throw an error for an empty list&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;lastItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LastOrDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// There are also several ways to go in the opposite direction:&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;firstIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstItem&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;anotherWayTofindIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;firstItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// If you want to find the last occurrence in the list:&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;lastIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LastIndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstItem&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;anotherWayTofindLastIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindLastIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;firstItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// There are also binary searches, but these should ONLY be used on sorted lists&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;firstIndex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BinarySearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"first string"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can sort lists with the &lt;code&gt;Sort&lt;/code&gt; method and it's overloads.&lt;/p&gt;

&lt;p&gt;Finally, there are options around copying lists:&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;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"first string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"second string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"third string"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;stringArray&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&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="c1"&gt;// Copy as an array&lt;/span&gt;
&lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CopyTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stringArray&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// copy as a list&lt;/span&gt;
&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;newStringList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stringArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The general list is available via the &lt;code&gt;System.Collections.Generic&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read-only List
&lt;/h3&gt;

&lt;p&gt;A read-only list, is a list that cannot have items added to it after initialization.  It's usually used when data has to be passed to a calling method, but you don't want the calling code to have access to change the list.&lt;/p&gt;

&lt;p&gt;As in the read-only dictionary, a read-only list can be declared like the following:&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;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;newList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&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;You can also create a read-only collection from a list like the following:&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;IReadOnlyCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;readOnlyCollection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsReadOnly&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Linked List
&lt;/h3&gt;

&lt;p&gt;At the top level, a linked list works the same way as a generic list.  However, it also contains additional methods to place items &lt;em&gt;relative&lt;/em&gt; to a position in the list. As a result, if you need to retrieve or add items relative to an item in the list, they work better.  Otherwise, it's better to use the normal &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Examples of some additional methods in a linked list are:&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;// Adding&lt;/span&gt;
&lt;span class="n"&gt;LinkedList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;linkedStringList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;LinkedList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;"second item"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;linkedStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddFirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"first item"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;linkedStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddLast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"last item"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;LinkedListNode&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;linkedListNode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;linkedStringList&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;linkedStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linkedListNode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"actually second item"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;linkedStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddBefore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;linkedStringList&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="s"&gt;"actually first item"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Removing&lt;/span&gt;
&lt;span class="n"&gt;linkedStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveFirst&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;linkedStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveLast&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linked lists are also available via the &lt;code&gt;System.Collections.Generic&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Immutable List
&lt;/h3&gt;

&lt;p&gt;An immutable list is a list that returns a new version of the list whenever an item is added or removed.  This means that the immutable list is &lt;code&gt;thread-safe&lt;/code&gt; provided you use &lt;code&gt;lock&lt;/code&gt; functionality in C#.  However, because the list creates an entirely new list every time items are added or removed, it can provide a performance hit over using a standard list.&lt;/p&gt;

&lt;p&gt;Immutable lists are a little strange, in that you don't create them the same way as a normal list as they don't have a public constructor:&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;ImmutableList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;immutableStringList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImmutableList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I said earlier, add and remove actually create new versions of the immutable list:&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;ImmutableList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;newImmutableStringList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;immutableStringList&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="s"&gt;"first"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sorted List
&lt;/h3&gt;

&lt;p&gt;A sorted list is a list that uses the &lt;code&gt;IEqualityComparer&lt;/code&gt; to sort the list. It &lt;em&gt;only&lt;/em&gt; supports key-value pairs and can be declared like the following:&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;SortedList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sortedStringList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SortedList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&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;In the same way that a Dictionary works, you cannot add duplicate keys to the list. However, where it differs is that it sorts lists based on the &lt;em&gt;value&lt;/em&gt;.  Here's an example of this in action:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://dotnetfiddle.net/Widget/aOKAPz" width="100%" height="600"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Sorted lists also live in the &lt;code&gt;System.Collections.Generic&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stacks and Queues
&lt;/h3&gt;

&lt;p&gt;Really, there's enough variations of stacks and heaps that they require their own article.  However, hey still are &lt;em&gt;technically&lt;/em&gt; lists as they maintain the order of a collection, just in slightly different ways. It's best to say that stacks and heaps in C# closely implement the &lt;a href="https://hackr.io/blog/stack-vs-heap" rel="noopener noreferrer"&gt;stack and heap&lt;/a&gt; data structures.&lt;/p&gt;

&lt;h4&gt;
  
  
  Stack
&lt;/h4&gt;

&lt;p&gt;While it's outside the scope of this article, a stack is a first-in-last-out data structure.&lt;/p&gt;

&lt;p&gt;If you're used to JavaScript, the syntax should be fairly familiar:&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;Stack&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;stackStringList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Add&lt;/span&gt;
&lt;span class="n"&gt;stackStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"first"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;stackStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"second"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;stackStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"third"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Check&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;third&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stackStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Peek&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;stackStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryPeek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;alsoThird&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Remove&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;thisIsthird&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stackStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;stackStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryPop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Queue
&lt;/h4&gt;

&lt;p&gt;Queues are essentially the opposite of a stack, in that they are first-in-first-out.&lt;/p&gt;

&lt;p&gt;You can use them like the following:&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;Queue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;queueStringList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="n"&gt;queueStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"first"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;queueStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"second"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;queueStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"third"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;string&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;queueStringList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dequeue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both the default stack and queue live in the &lt;code&gt;System.Collection.Generics&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Array
&lt;/h3&gt;

&lt;p&gt;An array is &lt;em&gt;also&lt;/em&gt; an ordered list of objects.  However, the big difference is that you need to declare how long an array is at initialization:&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;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;arrayOfFiveEmptyString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;arrayOfThree&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"one"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"two"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"three"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// changing the array to a length of five&lt;/span&gt;
&lt;span class="n"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;arrayOfThree&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Arrays are older data structures in C#, and while they are absolutely used within C# (such as a byte array), it's usually preferable to use a list due to the increased flexibility as arrays throw errors if you try to assign values outside the array's length.&lt;/p&gt;

&lt;p&gt;Arrays are fundamental to the operation of C# and as such live in the &lt;code&gt;System&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Array List
&lt;/h3&gt;

&lt;p&gt;An array list is the &lt;em&gt;forerunner&lt;/em&gt; to a modern list from the times before C# had generics.  As such an array list accepts a list of objects, rather than a generic.  This means that you do not benefit from type safety and as such, should be used with caution.&lt;/p&gt;

&lt;p&gt;For example, the following code does not throw errors:&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;ArrayList&lt;/span&gt; &lt;span class="n"&gt;arrayList&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;ArrayList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;arrayList&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="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;arrayList&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="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Array lists live within the &lt;code&gt;System.Collections&lt;/code&gt; namespace&lt;/p&gt;

&lt;h3&gt;
  
  
  Tag List
&lt;/h3&gt;

&lt;p&gt;Tag lists are a little strange, in that they are &lt;code&gt;structs&lt;/code&gt; and like the sorted list, accept key-value pairs. However, essentially they're used when you need to optimize for memory usage as they avoid allocating memory when you have 8 or fewer items in the collection.  They also accept objects in the value field, rather than generics, meaning that you don't have the advantage of type safety when using them.&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;TagList&lt;/span&gt; &lt;span class="n"&gt;tagList&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;TagList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;tagList&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="s"&gt;"tag"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;tagList&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="s"&gt;"tagTwo"&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tagList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt;&lt;span class="n"&gt;t&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="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// throws an exception&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;exceptionTag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tagList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;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="n"&gt;t&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="m"&gt;1&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;tagTwo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tagList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Last&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;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="n"&gt;t&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="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tag lists actually live within the &lt;code&gt;System.Diagnostics&lt;/code&gt; namespace&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>lists</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A quick tour of dictionaries in C#</title>
      <dc:creator>Jack Lewis</dc:creator>
      <pubDate>Fri, 21 Apr 2023 19:10:53 +0000</pubDate>
      <link>https://dev.to/jlewis92/a-quick-tour-of-dictionaries-in-c-gg9</link>
      <guid>https://dev.to/jlewis92/a-quick-tour-of-dictionaries-in-c-gg9</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;Dictionaries in C# are one of the more useful data structures you can use to store and retrieve data quickly and most programming languages have some form of dictionary built into them.  However, there are a lot more versions of dictionary in C# and I haven't an article bringing them all together to discuss where you'd use one over the other, so hopefully this might help you out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's a dictionary?
&lt;/h2&gt;

&lt;p&gt;Put simply, a dictionary is a list of key-value pairs.  It's mainly used when you need to store large or complex objects within the code while also optimizing for quick retrieval of data. It does this by allowing you to pull out a key (which has to be unique) that is linked to the larger object (the value).  However, while this was original intent, dictionaries are regularly used for 'simpler' retrieval such as pairs of strings as they are also useful guaranteeing uniqueness in a collection.  Dictionaries are also (usually) part of the generic namespace in C# because the type of the key and value is declared at initialization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dictionary types
&lt;/h2&gt;

&lt;p&gt;As I mentioned earlier there's a fair number of &lt;em&gt;types&lt;/em&gt; of dictionary in C# so let's go through them now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dictionary
&lt;/h3&gt;

&lt;p&gt;This is the "default" dictionary.  It's pretty decent in most scenarios where you need one and is also the 'oldest' form of dictionary in C#.  Given this is the default, I'm going to run through some common features of a dictionary within C#.&lt;/p&gt;

&lt;p&gt;You can declare this dictionary like this:&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;Dictionary&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;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myDictionary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&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;,&lt;/span&gt; &lt;span class="kt"&gt;string&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;This form of dictionary allows you to add and remove entries at will:&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;myDictionary&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="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"stuff"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;myDictionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryAdd&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="s"&gt;"stuff"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// You only remove via the key&lt;/span&gt;
&lt;span class="n"&gt;myDictionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&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="n"&gt;myDictionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you can find items in a dictionary in a few different ways (though I'd probably avoid linq):&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;myDictionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ContainsKey&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="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myDictionary&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="c1"&gt;// This is actually coming from the IReadOnlyDictionary interface&lt;/span&gt;
&lt;span class="n"&gt;myDictionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetValueOrDefault&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="n"&gt;myDictionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&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="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;myDictionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&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="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You probably noticed there's a few methods available are prefixed by &lt;code&gt;Try&lt;/code&gt;.  This is because the default of a dictionary is to throw an &lt;code&gt;ArgumentException&lt;/code&gt; if the method does not return positively. As a result, C# provides some methods that allow you to &lt;em&gt;safely&lt;/em&gt; retrieve values without having to resort to try/catch blocks which can be slow.&lt;/p&gt;

&lt;p&gt;Interestingly, because dictionaries are generic, you can actually chain them together like this:&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;Dictionary&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;,&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myChainedDictionary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&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;,&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is probably something I wouldn't recommend outside some very specific situations, but it's possible.&lt;/p&gt;

&lt;p&gt;Finally, a dictionary is not thread-safe by default, but you can introduce this feature using locks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read-Only Dictionary
&lt;/h3&gt;

&lt;p&gt;A read only dictionary works the same way under the hood as a normal dictionary only that values can only be added during initialization.  What this means, is that there are no &lt;code&gt;add&lt;/code&gt; and &lt;code&gt;remove&lt;/code&gt; methods available.&lt;/p&gt;

&lt;p&gt;There are actually 2 ways a read only dictionary can be declared, although one of them is via an interface:&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;IReadOnlyDictionary&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;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myReadOnlyDictionary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&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;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&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="s"&gt;"stuff"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// This lives in System.Collections.ObjectModel&lt;/span&gt;
&lt;span class="n"&gt;ReadOnlyDictionary&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;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myReadonlyDictionary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ReadOnlyDictionary&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;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;myDictionary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a small difference between these methods as the &lt;code&gt;ReadOnlyDicitonary&lt;/code&gt; has a &lt;code&gt;TryAdd&lt;/code&gt; method, but attempting to call it will cause a &lt;code&gt;NotSupportedException&lt;/code&gt; to be thrown.&lt;/p&gt;

&lt;p&gt;The main &lt;em&gt;reason&lt;/em&gt; to have these methods available is when you want to make data in a dictionary available to callers, but don't want to allow a caller to modify the collection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Immutable Dictionary
&lt;/h3&gt;

&lt;p&gt;An immutable dictionary works in a very similar manner to the standard dictionary in C# &lt;em&gt;but&lt;/em&gt; whenever you &lt;code&gt;add&lt;/code&gt; or &lt;code&gt;remove&lt;/code&gt; an item from the dictionary, it creates a copy of the dictionary, adds the item and then returns the new collection to you.  This means that the underlying object stays the same and therefore an immutable dictionary is &lt;code&gt;thread-safe&lt;/code&gt; compared to previous entries in this list.  However, because new dictionaries are created every time an immutable dictionary is updated, there is a performance impact that isn't found on a standard dictionary.&lt;/p&gt;

&lt;p&gt;This dictionary type also lives in the &lt;code&gt;System.Collections.Immutable&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sorted Dictionary
&lt;/h3&gt;

&lt;p&gt;The sorted dictionary is really quite straightforward, it will &lt;em&gt;sort&lt;/em&gt; the dictionary based on the value of the key.&lt;/p&gt;

&lt;p&gt;I've written a DotNetFiddle showing this in practice below:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://dotnetfiddle.net/Widget/CDPsuT" width="100%" height="600"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Sorted dictionaries are really only useful when you need the data to be sorted (an example I've seen is using DateTime as a key and needing to retrieve anything between 2 dates).  This is because a sorted dictionary is much slower than a standard dictionary due to the increased overhead sorting has when adding and fetching items.  There are more details on this found &lt;a href="https://www.dotnetperls.com/sorteddictionary" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concurrent Dictionary
&lt;/h3&gt;

&lt;p&gt;There's a really good in-depth article found &lt;a href="https://code-maze.com/concurrentdictionary-csharp/" rel="noopener noreferrer"&gt;here&lt;/a&gt; around how a concurrent dictionary works, but essentially it's a dictionary that's designed to run safely across multiple threads (as described by the "concurrent" part).  However, it works very differently from an immutable dictionary and does not return a new dictionary every timer a value is added.  The concurrent dictionary was added fairly recently to C# and it's what I'd recommend if you need to access to a shared dictionary.&lt;/p&gt;

&lt;p&gt;Frozen dictionaries do have a really useful method called &lt;code&gt;AddOrUpdate&lt;/code&gt; which will attempt to add a value to the dictionary, if the key already exists, it will update the value.  This method uses function within itself to choose whether to update the value, or keep it the same&lt;/p&gt;

&lt;p&gt;Here's an example of this:&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;ConcurrentDictionary&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;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;concurrentDictionary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ConcurrentDictionary&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;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="n"&gt;concurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOrUpdate&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="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;concurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOrUpdate&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="s"&gt;"someValue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"valueTwo"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;concurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOrUpdate&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="s"&gt;"someValue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"valueTwo"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;concurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOrUpdate&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="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"valueTwo"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;concurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOrUpdate&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="s"&gt;"someValue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// this results in the following dictionary:&lt;/span&gt;
&lt;span class="c1"&gt;// key: 1, value: valueTwo&lt;/span&gt;
&lt;span class="c1"&gt;// key: 2, value: someValue&lt;/span&gt;
&lt;span class="c1"&gt;// key: 3, value: value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The concurrent dictionary lives in the &lt;code&gt;System.Collections.Concurrent&lt;/code&gt; namespace&lt;/p&gt;

&lt;h3&gt;
  
  
  Frozen Dictionary
&lt;/h3&gt;

&lt;p&gt;As of writing this article, frozen dictionaries are brand new to C#.  They share a lot of similarities between a read-only dictionary, in that once a frozen dictionary has been initialized, new values cannot be added to them.  However, there's a major difference between the two in that frozen dictionaries are heavily optimized for read speed as opposed to the read-only dictionary, which is closer to a wrapper on the normal dictionary class.&lt;/p&gt;

&lt;p&gt;What this means, is that frozen dictionaries take longer to initialize than a regular dictionary, but retrieving values is much faster.  There's a comment &lt;a href="https://github.com/dotnet/runtime/issues/67209#issuecomment-1298780079" rel="noopener noreferrer"&gt;here&lt;/a&gt; on the GitHub repository for .Net showing that a frozen implementation of hash set was over twice as quick to get a value, as compared to a regular hash set. As a consequence, the use case for a frozen dictionary is for long-lived collections of data that doesn't change.&lt;/p&gt;

&lt;p&gt;The frozen dictionary lives in the &lt;code&gt;System.Collections.Frozen&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hash Table
&lt;/h3&gt;

&lt;p&gt;OK, a hash set isn't &lt;em&gt;technically&lt;/em&gt; a dictionary, but it performs a function similar enough that worth mentioning in this article as they are essentially, a collection of key-value pairs that require unique keys.&lt;/p&gt;

&lt;p&gt;The first difference between a hash table and a dictionary is that they aren't generic, they instead accept &lt;em&gt;objects&lt;/em&gt;. The difference is subtle when you're new to C#, but basically, generics are quicker and also signpost errors better than when you're using objects.&lt;/p&gt;

&lt;p&gt;This means a hash table can be declared like this:&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;Hashtable&lt;/span&gt; &lt;span class="n"&gt;myHashTable&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;Hashtable&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Frankly, while hash tables exist in C#, I would be cautious of using them given how dangerous they can be around typing.  For example:&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;myHashTable&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="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"stuff"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// this line will throw an error at runtime&lt;/span&gt;
&lt;span class="c1"&gt;// myHashTable.Add(1, "stuffTwo");&lt;/span&gt;

&lt;span class="c1"&gt;// this won't&lt;/span&gt;
&lt;span class="n"&gt;myHashTable&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="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"stuffTwo"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// this results in the following hash table:&lt;/span&gt;
&lt;span class="c1"&gt;// key: 1, value: stuff&lt;/span&gt;
&lt;span class="c1"&gt;// key: "1", value: stuffTwo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, you can use multiple similar keys with a different type in a hash table, and most crucially, &lt;em&gt;it doesn't complain when you get the type wrong&lt;/em&gt;. This can become a serious issue when you start objects throughout the code, which can be less obvious on if the types match.&lt;/p&gt;

&lt;p&gt;The hash table can be found in the &lt;code&gt;System.Collections&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  List of Tuples
&lt;/h3&gt;

&lt;p&gt;This is most definitely not a dictionary, but it still stores 2 (or more) values in a list.  This should be used when all values are of equal importance, and you actually want the duplicates in the list.  To be absolutely clear, a tuple is &lt;em&gt;not&lt;/em&gt; a key-value pair, it's better described as value-value pair and can in fact be several values (i.e.: value-value-value-value etc).  A contrived example of using a list of tuples could be stock tracking system where all you care about is what's been sold and how much.  In this case you'd want to know that you'd had 10 socks leave the warehouse on several occasions, rather than that somebody had removed 10 socks from the warehouse on at least 1 occasion that a dictionary would give you.&lt;/p&gt;

&lt;p&gt;A list of tuples can also use &lt;em&gt;named&lt;/em&gt; values which makes it easier to keep track of what the values mean, as compared to a dictionary which is always named &lt;code&gt;key&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt;.  For example:&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;List&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="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;myTupleList&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&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;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;();&lt;/span&gt;

&lt;span class="n"&gt;myTupleList&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="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"socks"&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;numberOfFirstItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myTupleList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;number&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 find tuples useful when you want to return more than 1 object from a method, without having to resort to the &lt;code&gt;out&lt;/code&gt; parameter in C#.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>dictionaries</category>
      <category>beginners</category>
    </item>
    <item>
      <title>7 JavaScript libraries to help express yourself on DEV</title>
      <dc:creator>Jack Lewis</dc:creator>
      <pubDate>Fri, 14 Apr 2023 18:01:22 +0000</pubDate>
      <link>https://dev.to/jlewis92/7-javascript-librarires-to-help-express-yourself-on-dev-364c</link>
      <guid>https://dev.to/jlewis92/7-javascript-librarires-to-help-express-yourself-on-dev-364c</guid>
      <description>&lt;p&gt;I've written a few articles on DEV now and one of the interesting things you can do on DEV is to use &lt;a href="https://dev.to/p/editor_guide"&gt;liquid tags&lt;/a&gt; to embed websites into a post.  There's quite a few of them that are related to programming sandboxes such as &lt;a href="https://codepen.io/" rel="noopener noreferrer"&gt;CodePen&lt;/a&gt; which you can use to embed programming directly into an article on DEV.  As I've explained in a &lt;a href="https://dev.to/jlewis92/tips-and-tricks-on-how-to-keep-motivated-while-writing-code-5h3b"&gt;previous article&lt;/a&gt; anything which &lt;em&gt;pushes you to write code&lt;/em&gt; is a great thing to help keep your skills up.  As a result, I quite like the idea of embedding programming into my articles as it's a bit of a sneaky way to push me to write more code. Additionally, having a complete app that you can view and play with is &lt;em&gt;really&lt;/em&gt; useful for understanding exactly how I did something.&lt;/p&gt;

&lt;p&gt;I'm limiting this to JavaScript/TypeScript currently as this is the most common language used to write front ends and therefore has a lot of libraries written with the intention of displaying information visually.  However, if you're learning a specific language, there's usually a liquid tag associated with. For example, C# has liquid tags support for &lt;a href="https://dotnetfiddle.net/" rel="noopener noreferrer"&gt;DotNetFiddle&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/mermaid-js/mermaid" rel="noopener noreferrer"&gt;Mermaid.js&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Here's a codepen I've written for a previous &lt;a href="https://dev.to/jlewis92/a-developers-guide-to-estimation-3143"&gt;article around estimation&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/jlewis92/embed/JjaMyxP?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This isn't the prettiest example of the kind of output you can get from Mermaid, but it at least shows how you can build a flowchart out using a code based solution.  Flowcharts aren't the only thing you can do with Mermaid and there's a full list in the link above, but you can also write UML like class, state and sequence diagrams and also pie and Gantt charts.  While I've found writing these diagrams does take longer than using something like &lt;a href="https://app.diagrams.net/" rel="noopener noreferrer"&gt;draw.io&lt;/a&gt; (which is also written in JavaScript) it does entirely do away with manual formatting of a diagram, which can be the hardest thing to do when writing a diagram.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/carbon-app/carbon" rel="noopener noreferrer"&gt;Carbon&lt;/a&gt;
&lt;/h2&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%2Fkup2ai98x6qkr55c2u6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkup2ai98x6qkr55c2u6g.png" alt="Carbon screenshot" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OK, this isn't a tool to &lt;em&gt;directly&lt;/em&gt; write code and also is probably a worse option on dev as you can't copy and paste the code directly.  However, for simply showing a beautiful screenshot of code quickly, this is an excellent tool.  Carbon supports a wide variety of languages, can directly pull out an image from &lt;a href="https://unsplash.com/" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt; to use as the background and has direct integration editors, including vscode.  Additionally, Carbon has tools to autoformat code, which is more difficult to do if you're directly writing code into the markdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/chartjs/Chart.js" rel="noopener noreferrer"&gt;Chart.js&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Simple chart:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/jlewis92/embed/VwEvgoP?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I think Chart.js is pretty self-explanatory.  It's used to make charts.  The above is a codepen based on the number of referrals I've received for one of my articles.  Just in this simple use case, it's easier to see the relative values of all the different places I'm getting viewers from, which is much easier to understand than the list that the default view gives.&lt;/p&gt;

&lt;p&gt;Charts.js is one of many charting libraries on GitHub, but it's currently the most popular with over 60k stars.  It supports 8 different chart types, animations, stacking graphs and as it's JavaScript based, Charts.js can be used to generate charts from code.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/jonobr1/two.js" rel="noopener noreferrer"&gt;Two.js&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/jonobr1/embed/MWEzMGv?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I want to point out, the above CodePen is made by the Two.js team, not me (and it's a lot more impressive than what I could do).  This is a 2D drawing library supporting WebGL, canvas2d and SVG.  Two.js can be used to make interactive code by binding to input devices via JavaScript.  There are a lot of examples of this happening found &lt;a href="https://two.js.org/examples/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/mathjax/MathJax" rel="noopener noreferrer"&gt;MathJax&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/jlewis92/embed/rNqOgJP?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;This is another one that might not be that useful as &lt;a href="https://katex.org/" rel="noopener noreferrer"&gt;Katex&lt;/a&gt; is supported through liquid tags. You can see an example of this same expression using the liquid tags &lt;a href="https://dev.to/jlewis92/a-developers-guide-to-estimation-3143"&gt;here&lt;/a&gt;. MathJax is essentially a library to support writing mathematical expressions through code using LaTeX, MathML, and AsciiMath notation, meaning it's a bit more modular than Katex.  Either of these tools will work though.  Additionally, if you're using a CodePen you can set the formatting how you want, rather than relying on the default in DEV.&lt;/p&gt;

&lt;p&gt;Simple motion canvas animation:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://player.vimeo.com/video/817732765" width="710" height="399"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;MotionCanvas is a way of describing animations via code for use in vide recording.  Granted, the primary use case of MotionCanvas is for video editing software and outputs individual frames rather than a full video and can't be built in a codepen.  However, it's not difficult to use FFmpeg to get a WebM.  There's a full video on how powerful MotionCanvas can be here:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/WTUafAwrunE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;
  summary
  &lt;br&gt;
If you want to generate a WebM using &lt;a href="https://ffmpeg.org/" rel="noopener noreferrer"&gt;FFmpeg&lt;/a&gt;, the following command being run from the output directory to do this:&lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ffmpeg -framerate 60 -i %6d.png -c:v libvpx-vp9 -lossless 1 -pix_fmt yuva420p output.webm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/cytoscape/cytoscape.js" rel="noopener noreferrer"&gt;Cytoscape.js&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;CodePen showing things on my desk:&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/jlewis92/embed/VwEedrw?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Cytoscape is another graphing library, but specifically built around networks.  It's pretty straightforward to show complex relationships between objects.  For example, this impressive diagram &lt;a href="https://js.cytoscape.org/demos/tokyo-railways/" rel="noopener noreferrer"&gt;showing railways in Tokyo&lt;/a&gt; that also integrates travelling salesmen problem-solving.  Cytoscape is particularly useful as it helps to visualize objects in a &lt;a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts-ai/vectordb" rel="noopener noreferrer"&gt;vector database&lt;/a&gt;, which given it feels like everything is moving towards AI recently, it's probably nice to know.  The above CodePen also integrates &lt;a href="https://github.com/dagrejs/dagre" rel="noopener noreferrer"&gt;dagre&lt;/a&gt; to simplify layout.&lt;/p&gt;

</description>
      <category>dev</category>
      <category>codepen</category>
      <category>javascript</category>
      <category>expression</category>
    </item>
    <item>
      <title>Tips and tricks on how to keep motivated while writing code</title>
      <dc:creator>Jack Lewis</dc:creator>
      <pubDate>Fri, 07 Apr 2023 19:25:35 +0000</pubDate>
      <link>https://dev.to/jlewis92/tips-and-tricks-on-how-to-keep-motivated-while-writing-code-5h3b</link>
      <guid>https://dev.to/jlewis92/tips-and-tricks-on-how-to-keep-motivated-while-writing-code-5h3b</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I've been programming professionally for nearly a decade now (and casually for a lot longer than that!), and while I'm still learning new things every day, there are certain things I've learnt over the past 10 years that I keep finding have helped me keep writing code and learning new things.  For me, software development is one of those careers where you need to keep learning as what was a great choice even last year, can quickly become a terrible choice today.  As a result, &lt;em&gt;anything&lt;/em&gt; which pushes me to do some work in my spare time is helpful to me personally and professionally.  Finally, in my experience the best way to learn something is to &lt;em&gt;use&lt;/em&gt; it as opposed to just reading about it, as it teaches you the pitfalls and idiosyncrasies that online guides can sometimes miss. I should note, that these are things that I've found &lt;em&gt;help me&lt;/em&gt;, and this isn't an exhaustive list.&lt;/p&gt;

&lt;h3&gt;
  
  
  'Fun' Money
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjlewis92%2FDevToArticleRepo%2Fmain%2Fmarkdown%2Fassets%2Fdev%2FproductivityTips%2Fmicheile-henderson-ZVprbBmT8QA-unsplash.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%2Fraw.githubusercontent.com%2Fjlewis92%2FDevToArticleRepo%2Fmain%2Fmarkdown%2Fassets%2Fdev%2FproductivityTips%2Fmicheile-henderson-ZVprbBmT8QA-unsplash.jpg" alt="Money" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Really, the first few tips come down to equipment choice that I'll discuss later.  However, the thing which I will always advocate is keep a small pot of money every month to the side that will purely go towards something technical.  This doesn't need to be used up every month, or be a large amount of money, just something that is affordable to you every month.  The reason for this is that if you get a new toy, you want to use it, and I usually see a marked increase in time spent after purchasing something like this.  Related to this though, I'd avoid subscription services as these can quickly get out of hand.  The sort of stuff I look at is some stuff on humble bundle, or a new interesting piece of hardware that involves me learning to use it. Smart home products are great for this as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interesting Personal Project
&lt;/h3&gt;

&lt;p&gt;This seems kind of obvious, but actually working on a project as opposed to a contrived coding challenge pushes me to work harder in my spare time.  Reasons for this working are that you're working towards a concrete goal that can take multiple days to get to a final product, as opposed to coding challenges which usually have multiple defined end points that can make you want to stop after completing a certain number of them.  Additionally, a personal project usually mixes up several techniques, packages and languages which means that a single project can help you to learn multiple technologies at once in a way that can be quicker than learning each of them individually as one of the harder tasks in software development is integration work.&lt;/p&gt;

&lt;p&gt;By interesting, I mean pick something that you're &lt;em&gt;excited&lt;/em&gt; to work on as opposed to purely wanting to learn that tech, or for commercial purposes.  This is because in my opinion, the hardest part about not working on the code, but &lt;em&gt;building the motivation to work on the project&lt;/em&gt;.  It's no good having the coolest personal project you can think of, if you just aren't motivated to work on it.  It doesn't &lt;em&gt;matter&lt;/em&gt; that somebody else has already built the same thing, what matters is that it gets you excited to work on the project.&lt;/p&gt;

&lt;h4&gt;
  
  
  Keep them short
&lt;/h4&gt;

&lt;p&gt;When choosing to work on a personal project, it's tempting to talk about an all singing all dancing project that will do everything better than anybody else has ever done before.  I would avoid doing this, as it leads to pretty heavy demotivation when you realize how long it will take to actually write the project.  Just take a look at the roots of some of today's largest technology companies, the first product is vastly different from the slick and complex websites and software we see from them today.  For me, I would look at a project which will take anywhere from 2 weeks to 2 months to complete as a perfect project, and definitely less than 6 months.&lt;/p&gt;

&lt;h4&gt;
  
  
  Mix the known with the unknown
&lt;/h4&gt;

&lt;p&gt;One of the major reasons for writing a personal project is to learn new things.  However, as I said earlier the hardest part is keeping up motivation.  By mixing the known with the unknown, you get a good mix of learning new things and the ability to switch context when you get frustrated with something not working, which gives you time to step back and reevaluate whether what you're trying to do is the best way to do it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Time management
&lt;/h4&gt;

&lt;p&gt;I've previously written &lt;a href="https://dev.to/jlewis92/a-developers-guide-to-estimation-3143"&gt;an article about how to estimate&lt;/a&gt;.  Some of the things I've discussed there are pretty relevant to just writing stuff in general.  Specifically I mean timeboxing.  This where you say to yourself "I have 3 hours now, in that time I have to get feature X to do Y".  If you fail, no big deal but just by fixing in your head a concrete goal, I find it helps to actually complete the goal, as well as giving you a minor dopamine rush when you do what you said you would.&lt;/p&gt;

&lt;h4&gt;
  
  
  Be realistic
&lt;/h4&gt;

&lt;p&gt;This is pretty related to keeping a project short, but you're (probably) a single developer working on a project, don't expect to be able to write a 100 000 line project by yourself without burning out.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keyboard and Mouse
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjlewis92%2FDevToArticleRepo%2Fmain%2Fmarkdown%2Fassets%2Fdev%2FproductivityTips%2Fjay-zhang-ZByWaPXD2fU-unsplash.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%2Fraw.githubusercontent.com%2Fjlewis92%2FDevToArticleRepo%2Fmain%2Fmarkdown%2Fassets%2Fdev%2FproductivityTips%2Fjay-zhang-ZByWaPXD2fU-unsplash.jpg" alt="Keyboard" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These tools are the primary way you interact with a computer, so having a satisfying way of interfacing with a computer pushes you to write more.  As a result, I'd spend a fair portion of my budget on a really nice mechanical keyboard as the feel of typing and the sound makes me want to type more, meaning that I spend less time on taking breaks when I should be working and also lets me work longer as the ergonomics of a good keyboard make typing less tiring.  Only thing I'd say though, is be mindful of if you have to work around other people as a loud keyboard can be extremely distracting.  In terms of a mouse, it's exactly the same as I find it just uncomfortable to work with a small, cheap mouse as my hand would start to cramp up with extended use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Music
&lt;/h3&gt;

&lt;p&gt;This is heavily personal preference and I know there are a lot of people who like to work in silence, but I pretty much always have music running when I'm writing.  The reason for this is I find it easy tune out, and it masks the far more annoying sounds I can hear nearby, such as road noise or somebody talking loudly into a phone.  Compared to this, music is a vast improvement.  I also use a smart speaker so that if I want to change songs because I don't like the current one, I can just shout at it, rather than having to stop what I'm doing and press a button to skip.&lt;/p&gt;

&lt;h3&gt;
  
  
  Good (maybe not great) Computer
&lt;/h3&gt;

&lt;p&gt;This might be a slightly controversial take, but you don't &lt;em&gt;need&lt;/em&gt; the latest and greatest hardware to code effectively, and it might actually be a good idea to choose something a bit more cost-efficient and spend the saved money on something else related to software development. However, while it might be a little redundant to say today, I would say that an SSD is mandatory just for the reduced boot time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Second Monitor
&lt;/h3&gt;

&lt;p&gt;Research has shown that using a second monitor can &lt;a href="https://www.jonpeddie.com/news/jon-peddie-research-multiple-displays-can-increase-productivity-by-42/" rel="noopener noreferrer"&gt;boost productivity by over 40%&lt;/a&gt; which I think is probably good enough to leave there.  However, from personal experience writing code on one monitor, and having a webpage running on the second monitor makes it so much easier to reference what I'm doing, without constantly having to tab between windows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical Writing
&lt;/h3&gt;

&lt;p&gt;This is another "from personal experience" point, but having an audience reading about what I'm doing absolutely is pushing me to spend more of my time working on projects so that I have something interesting to push out.  As a result, &lt;em&gt;writing up&lt;/em&gt; what you've done can help make you complete a project, as well as allowing you to brush up on a very important skill that can be left by the wayside for being boring - &lt;em&gt;documentation&lt;/em&gt;. Also, knowing that somebody might actually (god forbid) &lt;em&gt;look at the code I'm writing&lt;/em&gt; makes me spend more time on writing the code well, because making silly mistakes is embarrassing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get Inspired
&lt;/h3&gt;

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

&lt;p&gt;One of the things that I find helps me to keep up my personal work is to look the interesting stuff other people are doing.  The best place I've found for this is the &lt;a href="https://github.com/trending" rel="noopener noreferrer"&gt;GitHub trending page&lt;/a&gt;.  This helps as some of the most interesting project ideas I've had, is to look at something somebody else has made (or thought of), and then realize I could apply something similar to a totally different problem.&lt;/p&gt;

&lt;h4&gt;
  
  
  Work with someone else's project
&lt;/h4&gt;

&lt;p&gt;By this I mean, &lt;em&gt;actually use something somebody else has written&lt;/em&gt;.  You can just look at any one of the awesome lists on GitHub and I can pretty much guarantee you will find an interesting project to test out.  These can turn in to full on projects themselves which can teach you about concepts &lt;em&gt;related&lt;/em&gt; to software development, but don't directly impact them.  For example, I have a really long article &lt;a href="https://dev.to/jlewis92/how-and-why-i-built-my-home-network-from-scratch-1ii4"&gt;describing how I built my home network&lt;/a&gt;. I did very little coding, but it taught me a ton about networking I didn't know before.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relation to other interests
&lt;/h3&gt;

&lt;p&gt;A great way to keep you interested in continuing to write code, is to relate it to other interests you have.  This helps as you have increased domain knowledge over something you've never done before which can mean that you need to do less research and reduce the possibility of hitting a dead end that you didn't realize was there before you'd spent several hours working on the dead end.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoid Becoming Your Job
&lt;/h3&gt;

&lt;p&gt;I really enjoy coding, and I've worked a lot on fascinating projects professionally, but there's just something about writing code for myself that's so much more &lt;em&gt;interesting&lt;/em&gt; to me. This is most likely not because of the coding itself, but all the extra bits and pieces you have to deal with when writing a commercial product that are frankly, not that interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Take Breaks
&lt;/h3&gt;

&lt;p&gt;It can be tempting to spend every waking moment working on writing code when you get interested in what you're writing.  In my opinion, this can be detrimental as it leads to burn out pretty quickly which means you stop work.  Additionally, taking a break allows you to take a step back from the project, and check your assumptions are correct.  One of the ways to avoid this, is to set regular breaks even when you don't want to stop.  I've found the best way to force myself to do this, is to actually leave the house and go for walk or out for a meal or something.&lt;/p&gt;

&lt;h3&gt;
  
  
  Collaborate
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjlewis92%2FDevToArticleRepo%2Fmain%2Fmarkdown%2Fassets%2Fdev%2FproductivityTips%2Fmarvin-meyer-SYTO3xs06fU-unsplash.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%2Fraw.githubusercontent.com%2Fjlewis92%2FDevToArticleRepo%2Fmain%2Fmarkdown%2Fassets%2Fdev%2FproductivityTips%2Fmarvin-meyer-SYTO3xs06fU-unsplash.jpg" alt="Collaboration" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have other people depending on the work you're doing, this can push you to spend time working on the project and give you more motivation to continue as there is somebody else relying on you're output.  Working with other people also gives you the benefit of having a "rubber duck" you can bounce ideas off of and also gain confidence that what you're writing is actually the correct way of doing things when you agree.  Additionally, when you work with other people, varying interests mean that some the sections of writing code that you find boring, will be picked up by other developers as being interesting to them and vice versa.  For example, you might like working with object relational mappers and your friend enjoys building CI/CD pipelines.  There are limits to this of course (I think you'd struggle to find many people interested in writing DTO's) but can still help.&lt;/p&gt;

&lt;h3&gt;
  
  
  Failure Happens
&lt;/h3&gt;

&lt;p&gt;I think this is something very easy to say, but hard to put into practice.  However, it still needs saying.  I have entire folders of full of failed projects, and I think most developers would say something similar.  What's important though, is that you pick yourself up after a failed project, coding challenge or feature and &lt;em&gt;keep writing&lt;/em&gt;.  This doesn't have to be immediately, taking a break after something fails is a good idea, and it's natural to feel unmotivated afterwards.  Although, some of the things that can help afterwards are to use up the fun money (if you have any), work on something which isn't code based or go on a short holiday, and eventually you will find yourself interested in doing some coding again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Most Productive Time
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz5ue3v48lhj9h09apetv.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%2Fz5ue3v48lhj9h09apetv.jpg" alt="Productive Time" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the things that I find helps, is to figure out when you're most productive and plan to work on projects around that time.  For example, I'm a bit of a night owl and tend to find I'm most productive after dinner, so the majority of time, I tend to work on projects between 7pm and 10pm, which gives me enough time to "cool down" afterwards and not spend half the night thinking about what I could do next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final words
&lt;/h2&gt;

&lt;p&gt;I know a lot of these tips are pretty straightforward, but hopefully one or two of these tips are new to you and help you keep up motivation and help you to keep writing code, which as I said earlier, is the best way to keep learning new things and stay up to date on the latest in software development.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>beginners</category>
      <category>motivation</category>
      <category>tips</category>
    </item>
    <item>
      <title>Great programs to help out any software developer</title>
      <dc:creator>Jack Lewis</dc:creator>
      <pubDate>Fri, 31 Mar 2023 16:25:52 +0000</pubDate>
      <link>https://dev.to/jlewis92/great-programs-to-help-out-any-software-developer-4mnj</link>
      <guid>https://dev.to/jlewis92/great-programs-to-help-out-any-software-developer-4mnj</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Whenever you decide to develop software for the first time it can be a little daunting to know where to get started on actually &lt;em&gt;developing&lt;/em&gt; software.  There is a dizzying array of tools and programs which can be used to develop pretty much anything, while also being told all you need is notepad.  This is a list of some of the tools I use (and some alternatives) to help out when I'm developing software.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caveats
&lt;/h3&gt;

&lt;p&gt;It does need to be said before I write anything down that this list is highly subjective and if you have a tool which works great for you, then it's better than whatever I'm recommending.  Additionally, I'm going to keep these tools Windows specific, as that tends to be what I used to develop against.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software
&lt;/h2&gt;

&lt;p&gt;Some of these choices are going to be extremely obvious, but they're still worth repeating as I'm trying to write this article in a beginner-friendly manner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fjlewis92%2FDevToArticleRepo%2Fmain%2Fmarkdown%2Fassets%2Fdev%2FsoftwareDevTooling%2Fpexels-realtoughcandycom-11035539.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%2Fraw.githubusercontent.com%2Fjlewis92%2FDevToArticleRepo%2Fmain%2Fmarkdown%2Fassets%2Fdev%2FsoftwareDevTooling%2Fpexels-realtoughcandycom-11035539.jpg" alt="Git logo" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Starting off with one of the most recognizable tools in software development, Git is used to help track changes to software and helps to avoid losing code.  It was initially developed by Linus Torvalds for supporting Linux and has since grown into a massively popular tool used just about everywhere.&lt;/p&gt;

&lt;p&gt;Git can be a little difficult to get started with as it's a command line tool, but documents around its use can be found &lt;a href="https://git-scm.com/doc" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git GUI tools
&lt;/h3&gt;

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

&lt;p&gt;Keeping on the Git theme, Git itself can be confusing to learn when you're starting out and one of the ways I found to simplify learning Git was to use a GUI to start with.  There's a lot of them, all of which can help speed up software development if you use them right.  For myself, when I was starting out I used &lt;a href="https://www.sourcetreeapp.com/" rel="noopener noreferrer"&gt;SourceTree&lt;/a&gt;, before moving on to &lt;a href="https://desktop.github.com/" rel="noopener noreferrer"&gt;GitHub Desktop&lt;/a&gt;, but I don't use either of these any more because a separate client tended to actually slow me down.  Right now, I use &lt;a href="https://tortoisegit.org/" rel="noopener noreferrer"&gt;TortoiseGit&lt;/a&gt; because of its integration directly into the context menu in File Explorer and also the specific Git integrations that are built into the various tools I use to write code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Package managers
&lt;/h3&gt;

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

&lt;p&gt;Windows does have the ability to download packages similarly to Linux and these tools are called package managers.  There's 2 which tend to be most common in Windows &lt;a href="https://winget.run/" rel="noopener noreferrer"&gt;WinGet&lt;/a&gt; and &lt;a href="https://community.chocolatey.org/" rel="noopener noreferrer"&gt;Chocolatey&lt;/a&gt;.  There's a fair amount of history between these two package managers, but I'm currently using Chocolatey a lot more often.  The point of these programs is to vastly simplify the installation process for common software and also quickly share and download large numbers of packages and simplify the update process. Just be aware that Chocolatey (usually) needs to be run from an elevated command shell to work correctly.  A fair number of the programs I'm going to talk about today can be downloaded via Chocolatey.&lt;/p&gt;

&lt;p&gt;If you really don't want to mess around with a terminal, but have a new machine you want to install some of the most common tools to, you can use &lt;a href="https://ninite.com/" rel="noopener noreferrer"&gt;Ninite&lt;/a&gt; to build out a combined package as well.  However, this isn't really a package manager and more of a quick way to install software once using a GUI tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  Password managers
&lt;/h3&gt;

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

&lt;p&gt;As a developer, we have a lot of passwords and secrets that need to be remembered but only used very occasionally.  It should come as no surprise that a password manager of some description is needed to remember these passwords for you.  While most modern browser's come equipped with password manager features, they are heavily focussed on storing user passwords and nothing else and while you can force them to store more, they aren't built for it.  If you're looking for an all-in-one management solution, then unless you can self-host something like &lt;a href="https://github.com/dani-garcia/vaultwarden" rel="noopener noreferrer"&gt;VaultWarden&lt;/a&gt; or are okay dealing with the idiosyncrasies of something like the &lt;a href="https://support.nordpass.com/hc/en-us/articles/360006700458-Premium-vs-Free-version-of-NordPass" rel="noopener noreferrer"&gt;free version of NordPass&lt;/a&gt;, this is going to cost a monthly fee.  The largest player in the space is probably &lt;a href="https://www.lastpass.com/" rel="noopener noreferrer"&gt;LastPass&lt;/a&gt;.  This has upsides (lots more devs working on supporting the app) and downsides (most targeted for hacking), so really if you want to go this route, you'll need to do some research.&lt;/p&gt;

&lt;p&gt;Alternatively, if you don't mind missing out on some features (such as a built-in MFA authenticator), I use &lt;a href="https://keepassxc.org/" rel="noopener noreferrer"&gt;KeePassXC&lt;/a&gt;.  I use this particular password manager as it's cross-platform, relatively easy to share passwords across computers (using Google Drive) and has a fairly comprehensive feature set.  I did previously use KeePass, but stopped as that software is windows only meaning I couldn't use it on my phone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authenticator
&lt;/h3&gt;

&lt;p&gt;If you're using passwords to authenticate, you should also be enabling MFA to improve your security.  For this, you need an authenticator.  This can be hardware, using something like a &lt;a href="https://www.yubico.com/" rel="noopener noreferrer"&gt;YubiKey&lt;/a&gt;, or software based using an authenticator app.  As mentioned earlier, if you're using a tool like LastPass, there are some pretty great authenticators included. However, if you want a free version, there's the &lt;a href="https://www.microsoft.com/en-us/security/mobile-authenticator-app" rel="noopener noreferrer"&gt;Microsoft Authenticator&lt;/a&gt; that works on iOS and Android.  If you do choose to use the Microsoft Authenticator, make sure you turn on the cloud backup feature as this will save you a lot of pain if you lose or break your phone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Text Editor
&lt;/h3&gt;

&lt;p&gt;For me, a text editor is a pretty important part of my workflow.  I'm always copying text around, or needing to quickly format something and text editors can do this faster than a full-blown IDE or code editor.  When I'm evaluating a text editor, I'm looking for something that I can access via a file explorer context menu, and is really lightweight.  From this, I've found 3 options that work in this role. The first is notepad, that's built into Windows.  I'll use this when I just need to write some quick notes, but it's not what I'll reach for if there's anything more in-depth I need to do (such as formatting JSON and reading scripts).  The next is &lt;a href="https://www.vim.org/" rel="noopener noreferrer"&gt;vim&lt;/a&gt;, which I pretty much installed for evaluation when I was working on some Linux stuff. I rarely use it nowadays as it's fairly unintuitive, but will work in this role.  The option I use the most however, is &lt;a href="https://notepad-plus-plus.org/downloads/" rel="noopener noreferrer"&gt;notepad++&lt;/a&gt;.  The reason is that it's a lot more powerful than notepad and easier to use than vim.&lt;/p&gt;

&lt;h3&gt;
  
  
  IDE and Code Editors
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk4bfsj7ujizvoafil9vt.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%2Fk4bfsj7ujizvoafil9vt.jpg" alt="Visual Studio" width="800" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There was a distinction between an IDE and a code editor in the past where an IDE was considered powerful, but slow, with features like debugging and linting tools.  Whereas a code editor was less capable, but lightweight and designed to work with multiple languages.  However, I do think the lines have blurred heavily from computers getting more capable, and the release of tools like &lt;a href="https://neovim.io/" rel="noopener noreferrer"&gt;NeoVim&lt;/a&gt; and &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;Visual Studio Code&lt;/a&gt; that can do everything that an IDE can do and more traditional IDEs bringing in greater support for other languages, like Visual Studio &lt;a href="https://visualstudio.microsoft.com/vs/features/python/" rel="noopener noreferrer"&gt;working with Python&lt;/a&gt;.  My advice on this, is that I use the free language specific tool if there is one, such as &lt;a href="https://visualstudio.microsoft.com/" rel="noopener noreferrer"&gt;Visual Studio&lt;/a&gt; for C# and &lt;a href="https://www.eclipse.org/downloads/" rel="noopener noreferrer"&gt;Eclipse&lt;/a&gt; for Java.  I should note this is for personal development projects, in an organization I normally use a paid IDE, such as one of the many developed by &lt;a href="https://www.jetbrains.com/" rel="noopener noreferrer"&gt;JetBrains&lt;/a&gt; (like IntelliJ).  If I don't have a specific tool, or it's JavaScript based, I'll use Visual Studio Code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Browser
&lt;/h3&gt;

&lt;p&gt;I know this sounds extremely obvious, but the &lt;em&gt;choice&lt;/em&gt; of browser can actually be really important as (while this has gotten a lot better recently) different web browser's can render content differently (who else remembers the dreaded &lt;em&gt;this website only runs on Internet Explorer&lt;/em&gt;).  What this means is that if you want your website to look like what the vast majority of your user's experience it's a good idea to track something like the most popular browsers. For example, &lt;a href="https://www.w3schools.com/browsers/" rel="noopener noreferrer"&gt;the w3schools website&lt;/a&gt; shows that Google Chrome has a market share of nearly 80% across 60 million visits.&lt;/p&gt;

&lt;p&gt;This is one of the reasons I use Google Chrome as my primary browser, but I am hoping that &lt;a href="https://arc.net/" rel="noopener noreferrer"&gt;Arc&lt;/a&gt; comes to Windows, which is also based on Chromium.&lt;/p&gt;

&lt;h3&gt;
  
  
  Log Monitor
&lt;/h3&gt;

&lt;p&gt;There are two sides to this one, a centralized log management tool and a more general log monitoring tool that works like a modified text editor.  These are both really useful for different things.&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%2Fq1i2mvpncahxwn2mm97r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq1i2mvpncahxwn2mm97r.png" alt="Elastic" width="160" height="71"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First up is a centralized log management tool such as &lt;a href="https://www.splunk.com/" rel="noopener noreferrer"&gt;Splunk&lt;/a&gt; or &lt;a href="https://www.elastic.co/" rel="noopener noreferrer"&gt;elastic&lt;/a&gt;.  The reason for the existence of these tools is pretty straightforward: &lt;code&gt;if you have 100000 instances, and a 0.1% occurrence on a per instance basis, there are 100 examples of that issue in production&lt;/code&gt;.  While you might laugh about ever running 100000 instances of your software, this absolutely does happen in industry and there are many issues with a higher occurrence than 0.1%.  One of the ways you can track this is with a centralized log management tool, which will be able to tell you how common an issue is. Additionally, these systems are useful for real-time log monitoring which can pinpoint very quickly when there is a major issue occurring. While I wouldn't say it's the best idea to try and have one of these tools ready to go on your desktop as there are loads of them, understanding one of them can give you a deeper insight on how to manage and collate log files.  If you are still interested, I have a (short) tutorial on how to use &lt;a href="https://opensearch.org/" rel="noopener noreferrer"&gt;OpenSearch&lt;/a&gt; found &lt;a href="https://dev.to/jlewis92/building-a-devto-analytics-dashboard-using-opensearch-39m8"&gt;here&lt;/a&gt; that can also be used for centralized log management.&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%2Fnr07rgg3rsxtv5ppfnnx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnr07rgg3rsxtv5ppfnnx.png" alt="KLOGG" width="210" height="73"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second part to this, is a log monitoring tool.  These are useful for analysis rather than monitoring.  In my experience, once an error has been found, the most difficult errors to diagnose actually start throwing up signs in a log long before an actual error is shown, meaning that you need to hunt through a log file for some sort of error.  While you can upload entire log files to a centralized log management tool, this can cost a lot of money based on the amount of data that you're putting into one.  For example, I've personally witnessed an ElasticSearch cluster dealing with well over a terabyte of log files per week and this was several years ago.  You can probably imagine how expensive that was just to store it in AWS!  All this adds up to needing some form of tool to look through a log file.  While you can just use a text editor like Notepad++, a dedicated log monitor can vastly speed up and simplify the diagnosis process.  There are a few tools that can do this, such as &lt;a href="http://www.baremetalsoft.com/baretail/" rel="noopener noreferrer"&gt;BareTail&lt;/a&gt; (there's also a chocolatey package of the free version found &lt;a href="https://community.chocolatey.org/packages/baretail" rel="noopener noreferrer"&gt;here&lt;/a&gt;) or &lt;a href="https://klogg.filimonov.dev/" rel="noopener noreferrer"&gt;KLOGG&lt;/a&gt;, which is free and open source. The reason you would use a log monitor over a text editor is that they are optimized for opening massive log files quickly and provide a clear display of data, regex based highlighting tools (useful for support) and speed.  They also work much better over a slow network than a text editor in my experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terminal
&lt;/h3&gt;

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

&lt;p&gt;By terminal, I don't mean specific software related to a scripting language, PowerShell or Command Prompt.  I really mean one of the much newer combination terminal programs like the aptly named &lt;a href="https://github.com/microsoft/terminal" rel="noopener noreferrer"&gt;Terminal&lt;/a&gt; by Microsoft or &lt;a href="https://tabby.sh/" rel="noopener noreferrer"&gt;Tabby&lt;/a&gt;.  The benefit of these programs is that they allow you to run multiple terminals via a tabbed interface.  They both are pretty similar but with some differences.  For example, Terminal has access to the command palette found in vscode and Tabby has plugins that can be installed as well as a password vault built-in for secrets.  Both have the ability to customize the terminal appearance, run shells as an administrator and have customized shortcuts to simplify terminal use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sysinternals
&lt;/h3&gt;

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

&lt;p&gt;Probably less useful for a web developer, but these tools can be a lifesaver when trying to diagnose complex issues in a Windows environment.  This is a collection of tools developed to help show what is happening under the hood.  They can be downloaded &lt;a href="https://learn.microsoft.com/en-us/sysinternals/downloads/sysinternals-suite" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Really, there's far too many tools in this pack to go into any real detail, but can roughly be split into monitoring tools and helpers.  Some of the highlights within the monitoring tools are process explorer (a more powerful version of task manager), syslog and process monitor (both used to monitor logs in Windows - very useful for diagnosing issues with services!).  Essentially, if there's some information you would like to know about what's happening in Windows, there's probably a tool in Sysinternals that can help out.  On the helper side, there are tools like Autorun (that provides a detailed interface into running things on startup), Autologon (simplifies the automatic logon process setup in Windows) and ZoomIt (A series of tools used to make performing presentations easier).&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshot and recording tool
&lt;/h3&gt;

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

&lt;p&gt;While using the Windows default &lt;code&gt;prntscr&lt;/code&gt; button will get you perfectly adequate screenshots (especially when using &lt;code&gt;alt + prntscr&lt;/code&gt;) there's always a time when I want something a bit better.  There's even one already built into Windows! You can access the snipping tool using &lt;code&gt;Windows + shift + s&lt;/code&gt; which gives you the ability to select what &lt;em&gt;part&lt;/em&gt; of a screen you want to screenshot.&lt;/p&gt;

&lt;p&gt;However, the tool I personally use is &lt;a href="https://github.com/ShareX/ShareX" rel="noopener noreferrer"&gt;ShareX&lt;/a&gt;.  The benefits of this tool is everything that the snipping tool can do &lt;em&gt;and&lt;/em&gt; the ability to quickly record videos for demonstrations.  A video of how something works makes it so much easier for someone who is not so involved in the technical side to understand how something works and is easier for the developer as it reduces the need for time-consuming and stressful demonstrations.  It's also useful for test teams as it instantly shows how to reproduce a bug without playing email bingo over how to get a bug to manifest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker
&lt;/h3&gt;

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

&lt;p&gt;I think Docker should really be self-explanatory.  It sometimes feels like I can't go a single day without hearing &lt;em&gt;Somebody&lt;/em&gt; talking about Docker.  However, it's rightfully popular as it massively simplifies the process of running an application and offloads the complicated bit of managing a server.  A full explanation of Docker is outside the scope of this article (and you can probably find one if you spend 5 minutes browsing DEV), but in short it works by running applications in a container, which provides separation from the underlying infrastructure and helps to guarantee that the application will start the same way every time because they contain only the things needed to run an application. There's also a strong focus on programmatically building working containers so that updating and deploying the application can be done from a button press, rather than a manual upgrade process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Windows subsystem for Linux
&lt;/h3&gt;

&lt;p&gt;You can't really talk about Docker without also mentioning the tool it uses to run Linux images on Windows.&lt;/p&gt;

&lt;p&gt;The Windows Subsystem for Linux (usually shortened to wsl2) is a how you can get a Linux operating system running on top of Windows directly from &lt;a href="https://apps.microsoft.com/store/detail/ubuntu/9PDXGNCFSCZV" rel="noopener noreferrer"&gt;the Windows store&lt;/a&gt;.  It vastly simplifies the process if you need to work on something Linux, but don't have access to another machine.  This is an alternative solution to using a full-blown virtual machine like &lt;a href="https://www.virtualbox.org/" rel="noopener noreferrer"&gt;VirtualBox&lt;/a&gt; and on top of being heavily integrated into Windows, is a lighter weight (and therefore quicker) option.&lt;/p&gt;

&lt;h3&gt;
  
  
  PDF Reader
&lt;/h3&gt;

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

&lt;p&gt;While the vast majority of information found on a system is written on the internet, it's still pretty common to encounter a PDF file for documentation.  This goes triple of you're working directly with a supplier to integrate some closed source hardware or software.  At work, I do just use the in-built tools in chrome to view a PDF as it's easy, and I don't need to justify having it installed on my machine to a sysadmin. However, at home I use &lt;a href="https://www.foxit.com/" rel="noopener noreferrer"&gt;Foxit&lt;/a&gt; because it massively reduces the buffer time to &lt;em&gt;load&lt;/em&gt; a PDF and has a lot of features in the free version that aren't replicated in Adobe Acrobat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ending notes
&lt;/h2&gt;

&lt;p&gt;Once again, I do need to reiterate that this is &lt;em&gt;all&lt;/em&gt; personal preference, and frankly there's a lot of tools out there that are better than what I use, but in general I've found that everything I've used has helped me personally to be more productive.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>tools</category>
      <category>software</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building a dev.to analytics dashboard using OpenSearch</title>
      <dc:creator>Jack Lewis</dc:creator>
      <pubDate>Fri, 24 Mar 2023 17:31:39 +0000</pubDate>
      <link>https://dev.to/jlewis92/building-a-devto-analytics-dashboard-using-opensearch-39m8</link>
      <guid>https://dev.to/jlewis92/building-a-devto-analytics-dashboard-using-opensearch-39m8</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;So I was doing some looking through bits and pieces you can get access to within your DEV account and I noticed there was a rather interesting tab in my dashboard for analytics.  While I'm not a data scientist by any stretch of the imagination, it still pretty cool to see how you're doing in DEV.to.  So taking a look, there's some pretty cool features showing things like readers, new followers and reactions. For reference, this is what mine looks like as I'm writing this article:&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%2F8e8cdfu837e73q64n45f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8e8cdfu837e73q64n45f.png" alt="DEV analytics" width="724" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can &lt;em&gt;also&lt;/em&gt; get similar data out of an individual article.&lt;/p&gt;

&lt;p&gt;From looking at this I can see that I clearly did well at the beginning this month, and it's tapered off pretty quickly (maybe need to look in to writing something a bit more popular?).  I can see as well, that I've got 2 spikes in my reader graph that are related to an article I wrote around &lt;a href="https://dev.to/jlewis92/my-setup-for-publishing-to-devto-using-github-1k0n"&gt;my setup for posting to DEV.to&lt;/a&gt; and also &lt;a href="https://dev.to/jlewis92/how-to-programmatically-convert-desktop-git-repositories-to-a-new-remote-repository-1082"&gt;how to convert your local git repository to another remote programmatically&lt;/a&gt;.  Interestingly, my second popular article didn't have any corresponding engagement in DEV.to, which means that it was probably popular via another website, rather than directly from DEV.to.  Looking at the traffic summary for that second article, I can see this is true and that it's most popular on Twitter for some reason?&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%2Fnfamgjz9jpgx77ir58yj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnfamgjz9jpgx77ir58yj.png" alt="Traffic source summary" width="488" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anyway, getting a bit off track here, but after the first few minutes of looking at the analytics you can get out, there really isn't &lt;em&gt;that&lt;/em&gt; much information I can get out, and it's also pretty general i.e.: some of the things I'm interested in finding out are probably pretty niche.  So I spent some time thinking of if there was a way I could get some more information out that I wanted to know.  Turns out, there is!&lt;/p&gt;

&lt;h2&gt;
  
  
  Forem API
&lt;/h2&gt;

&lt;p&gt;The first step to figuring out if there's anything I can do to get more information, is to find out if there are any data sources that are accessible to me as a user.  Fortunately for me, DEV is built on an open source software platform known as Forem.  Luckily for me, it seems like somebody has &lt;em&gt;also&lt;/em&gt; wanted to be able to programmatically grab data out of DEV and there's &lt;a href="https://developers.forem.com/api" rel="noopener noreferrer"&gt;already an API that I can use to grab data from&lt;/a&gt;.  Being honest, if this wasn't here I likely would have stopped at this point, as while it would be possible to scrape data directly from the DEV, it would have been a pain that I didn't want to deal with so this API makes everything possible.&lt;/p&gt;

&lt;p&gt;While the API itself is incredibly useful, while writing this article there is currently 2 versions of the API which contain different endpoints:&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%2Fodfpz2k7x07o22ju3gz0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodfpz2k7x07o22ju3gz0.png" alt="Forem API" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This basically means I'm going to have to do some extra work later as both version contain useful stuff and I currently can't use the v1 API for everything.  However, as I'll talk about later I've got some ways to make this &lt;em&gt;really&lt;/em&gt; easy to implement later.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenSearch
&lt;/h2&gt;

&lt;p&gt;Now I know I've got some data I could use, I now need to find a platform that I can use to analyse the data coming from the Forem API. I did consider some other pieces of software, such as &lt;a href="https://cloud.google.com/bigquery" rel="noopener noreferrer"&gt;Google BigQuery&lt;/a&gt; (with looker studio) and &lt;a href="https://www.elastic.co/" rel="noopener noreferrer"&gt;ElasticSearch&lt;/a&gt; (with Kibana), I ultimately went with &lt;a href="https://opensearch.org/" rel="noopener noreferrer"&gt;OpenSearch&lt;/a&gt; which is essentially a forked version of ElasticSearch maintained by AWS.  The main reasons are that I could host it locally for free (unlike BigQuery).  I do have some prior experience with both elastic (back when it was called ELK) and OpenSearch, but my work with OpenSearch was far more recent, so I decided to go with that.&lt;/p&gt;

&lt;p&gt;OpenSearch also provides code libraries that allow you to directly interact with the OpenSearch database from code, which given I'm writing something new that's specifically supposed to interact with just OpenSearch and an API, makes it a much more straightforward to implement.  This is instead of going down the more 'traditional' route of analysing log files (which is the 'L' in ELK).&lt;/p&gt;

&lt;p&gt;OpenSearch really consists of 2 parts, the NoSQL database OpenSearch, and the data visualization tool known as OpenSearch Dashboard.  As a NoSQL database, OpenSearch doesn't really have the concept of "tables" but uses indexes instead, which work pretty similarly.  As with a fair number of other NoSQL databases, the schema for the data is figured out based on the data itself, rather than setup beforehand.  This does mean you can get into a mess if your data doesn't use standard data formats.  Fortunately, as Forem is using OpenAPI, this shouldn't be an issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;OpenSearch has an AWS managed service (of course) but the way I use it is via docker.  If you're using windows (like me) you can use &lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker Desktop&lt;/a&gt; to run the containers.  OpenSearch provides 3 different ways to run the docker container, these are running a single node via docker and a pair of docker-compose files.  I decided not to run in single node mode as I also wanted to run OpenSearch dashboard, which both docker-compose files allow you to do automatically.  In terms of the docker compose versions, there is a production version and a dev version, the difference being that the dev version has the security plugin disabled.  Given I'm just running locally and the security plugin takes some extra setup, I just went with the dev docker-compose file.&lt;/p&gt;

&lt;p&gt;The Docker compose itself, will create a pair of OpenSearch nodes in a cluster and a OpenSearch dashboard instance to view the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;version: '3'
services:
  opensearch-node1:
    image: opensearchproject/opensearch:latest
    container_name: opensearch-node1
    environment:
      - cluster.name=opensearch-cluster &lt;span class="c"&gt;# Name the cluster&lt;/span&gt;
      - node.name=opensearch-node1 &lt;span class="c"&gt;# Name the node that will run in this container&lt;/span&gt;
      - discovery.seed_hosts=opensearch-node1,opensearch-node2 &lt;span class="c"&gt;# Nodes to look for when discovering the cluster&lt;/span&gt;
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2 &lt;span class="c"&gt;# Nodes eligibile to serve as cluster manager&lt;/span&gt;
      - bootstrap.memory_lock=true &lt;span class="c"&gt;# Disable JVM heap memory swapping&lt;/span&gt;
      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" # Set min and max JVM heap sizes to at least 50% of system RAM
      - "DISABLE_INSTALL_DEMO_CONFIG=true" # Prevents execution of bundled demo script which installs demo certificates and security configurations to OpenSearch
      - "DISABLE_SECURITY_PLUGIN=true" # Disables security plugin
    ulimits:
      memlock:
        soft: -1 &lt;span class="c"&gt;# Set memlock to unlimited (no soft or hard limit)&lt;/span&gt;
        hard: -1
      nofile:
        soft: 65536 &lt;span class="c"&gt;# Maximum number of open files for the opensearch user - set to at least 65536&lt;/span&gt;
        hard: 65536
    volumes:
      - opensearch-data1:/usr/share/opensearch/data &lt;span class="c"&gt;# Creates volume called opensearch-data1 and mounts it to the container&lt;/span&gt;
    ports:
      - 9200:9200 &lt;span class="c"&gt;# REST API&lt;/span&gt;
      - 9600:9600 &lt;span class="c"&gt;# Performance Analyzer&lt;/span&gt;
    networks:
      - opensearch-net &lt;span class="c"&gt;# All of the containers will join the same Docker bridge network&lt;/span&gt;
  opensearch-node2:
    image: opensearchproject/opensearch:latest
    container_name: opensearch-node2
    environment:
      - cluster.name=opensearch-cluster &lt;span class="c"&gt;# Name the cluster&lt;/span&gt;
      - node.name=opensearch-node2 &lt;span class="c"&gt;# Name the node that will run in this container&lt;/span&gt;
      - discovery.seed_hosts=opensearch-node1,opensearch-node2 &lt;span class="c"&gt;# Nodes to look for when discovering the cluster&lt;/span&gt;
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2 &lt;span class="c"&gt;# Nodes eligibile to serve as cluster manager&lt;/span&gt;
      - bootstrap.memory_lock=true &lt;span class="c"&gt;# Disable JVM heap memory swapping&lt;/span&gt;
      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" # Set min and max JVM heap sizes to at least 50% of system RAM
      - "DISABLE_INSTALL_DEMO_CONFIG=true" # Prevents execution of bundled demo script which installs demo certificates and security configurations to OpenSearch
      - "DISABLE_SECURITY_PLUGIN=true" # Disables security plugin
    ulimits:
      memlock:
        soft: -1 &lt;span class="c"&gt;# Set memlock to unlimited (no soft or hard limit)&lt;/span&gt;
        hard: -1
      nofile:
        soft: 65536 &lt;span class="c"&gt;# Maximum number of open files for the opensearch user - set to at least 65536&lt;/span&gt;
        hard: 65536
    volumes:
      - opensearch-data2:/usr/share/opensearch/data &lt;span class="c"&gt;# Creates volume called opensearch-data2 and mounts it to the container&lt;/span&gt;
    networks:
      - opensearch-net &lt;span class="c"&gt;# All of the containers will join the same Docker bridge network&lt;/span&gt;
  opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:latest
    container_name: opensearch-dashboards
    ports:
      - 5601:5601 &lt;span class="c"&gt;# Map host port 5601 to container port 5601&lt;/span&gt;
    expose:
      - "5601" # Expose port 5601 for web access to OpenSearch Dashboards
    environment:
      - 'OPENSEARCH_HOSTS=["http://opensearch-node1:9200","http://opensearch-node2:9200"]'
      - "DISABLE_SECURITY_DASHBOARDS_PLUGIN=true" # disables security dashboards plugin in OpenSearch Dashboards
    networks:
      - opensearch-net

volumes:
  opensearch-data1:
  opensearch-data2:

networks:
  opensearch-net:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;&lt;em&gt;NOTE:&lt;/em&gt;&lt;/strong&gt; As I said earlier, this is NOT a production setup, this is a dev setup compose file, so should not be used outside a dev environment as you need the extra security.&lt;/p&gt;

&lt;p&gt;If this is the first time you've run a OpenSearch cluster in Docker Desktop you might notice that the containers crash out and complain about something like &lt;code&gt;vm.max_map_count is less than 262144&lt;/code&gt;.  This is because the cluster needs more resources than the Docker receives by default.  While you &lt;em&gt;can&lt;/em&gt; just write into the WSL subsystem to fix it each time, I like to set up the config file, so I don't get annoyed by having to do some command line setup after every restart. In order to fix this, you need to create a file in &lt;code&gt;C:\Users\&amp;lt;user running docker&amp;gt;&lt;/code&gt; called &lt;code&gt;.wslconfig&lt;/code&gt;.  The full file path would be something like &lt;code&gt;C:\Users\jack.lewis\.wslconfig&lt;/code&gt;, and then you then need to add the following to this file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[wsl2]&lt;/span&gt;
&lt;span class="py"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;4GB      # up's the memory from 2GB to 4GB&lt;/span&gt;
&lt;span class="py"&gt;processors&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;4    # up's the number of processors to 4&lt;/span&gt;
&lt;span class="py"&gt;kernelCommandLine&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"sysctl.vm.max_map_count=262144"&lt;/span&gt;   &lt;span class="c"&gt;# up's the max map count to the minimum required by OpenSearch
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;At this point, if you used the config I linked above, the containers should start and look like the following:&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%2Fzxfcyf8kkffmxt6vt3ws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzxfcyf8kkffmxt6vt3ws.png" alt="Docker desktop" width="800" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From this, what you need to know is that &lt;code&gt;http://localhost:9200&lt;/code&gt; is the address you need to use to push data into OpenSearch and &lt;code&gt;http://localhost:5601&lt;/code&gt; can be opened in the browser to look at OpenSearch dashboard.&lt;/p&gt;

&lt;p&gt;If you just want to explore the things OpenSearch can do, at this point you can open up dashboard, press the home button on the left-hand side, press &lt;code&gt;add sample data&lt;/code&gt; and it should give you a choice to add some sample data to play around with:&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%2Fu238drhqz2us3g2zd557.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu238drhqz2us3g2zd557.png" alt="OpenSearch sample data" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Finding data to analyse
&lt;/h2&gt;

&lt;p&gt;Immediately, I was &lt;em&gt;very&lt;/em&gt; happy to note that Forem provides an &lt;a href="https://swagger.io/specification/" rel="noopener noreferrer"&gt;OpenAPI spec document&lt;/a&gt; for both versions of the API, which is going to make it really easy to explore.&lt;/p&gt;

&lt;p&gt;All you need to do is download the spec, and then drag the file into &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; and you instantly have a quick way to explore all the endpoints in the Forem API:&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%2Fhlydspjfpwqfs7a5ljlw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhlydspjfpwqfs7a5ljlw.png" alt="Forem API in postman" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; if you want to actually return data, you need to set an API key at the top level folder (where it says DEV API (beta) for V0 and Forem API V1 for V1) and it's under variables.  You can get the DEV API key from &lt;a href="https://dev.to/settings/extensions"&gt;here&lt;/a&gt; and going to the point where it talks about DEV Community API Keys.&lt;/p&gt;

&lt;p&gt;I &lt;em&gt;think&lt;/em&gt; I might have also had to change the V1 base URL from &lt;code&gt;https://dev.to/api&lt;/code&gt; to &lt;code&gt;https://dev.to&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I think I really need to say though that the most common work I do day to day is building and integrating with API's.  From this I know how &lt;em&gt;difficult&lt;/em&gt; it is to get an OpenAPI spec right, and while the one from Forem &lt;em&gt;does&lt;/em&gt; have some minor issues, on the whole I've had to do less messing around with it than pretty anything else I've integrated previously - which is great!&lt;/p&gt;

&lt;p&gt;At this point, I can start exploring for data that would be useful and when I'm looking to integrate something into OpenSearch, I'm looking for 2 things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;WHAT happened&lt;/li&gt;
&lt;li&gt;WHEN it happened&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These, to me are the most important thing as OpenSearch is a time based analytics dashboard (when it happened) and the most common thing I really do in it is count how often something occurs (what happened).&lt;/p&gt;

&lt;p&gt;From this, I'm drawn to the &lt;a href="https://developers.forem.com/api/v1#tag/articles" rel="noopener noreferrer"&gt;user's published article's&lt;/a&gt; endpoint in V1 and the &lt;a href="https://developers.forem.com/api/v0#tag/followers" rel="noopener noreferrer"&gt;follower's&lt;/a&gt; endpoint in V0.  There's a lot more than this which could be useful, but at this point I'm pretty much just building a proof of concept, so these were the 2 endpoints I found that were most immediately useful to me (and honestly, the easiest to try and pull some useful stuff out of).&lt;/p&gt;
&lt;h2&gt;
  
  
  Integration
&lt;/h2&gt;

&lt;p&gt;At this point, I now need to glue everything together.  To do this, I need a program which can pull down data from Forem and then push it into the OpenSearch cluster.&lt;/p&gt;

&lt;p&gt;If you just want to take a look at the final solution, it lives here:&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/jlewis92" rel="noopener noreferrer"&gt;
        jlewis92
      &lt;/a&gt; / &lt;a href="https://github.com/jlewis92/ForemAnalyticsGatherer" rel="noopener noreferrer"&gt;
        ForemAnalyticsGatherer
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Retrieves analytics for forem
    &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;ForemAnalyticsGatherer&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;Retrieves analytics for forem&lt;/p&gt;

&lt;p&gt;The command line arguments supported are as follows:&lt;/p&gt;

&lt;div class="highlight highlight-source-powershell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt; &lt;span class="pl-k"&gt;-&lt;/span&gt;a&lt;span class="pl-k"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;--&lt;/span&gt;articleGatherer     (&lt;span class="pl-k"&gt;Default&lt;/span&gt;: true) Whether the article gatherer is enabled

  &lt;span class="pl-k"&gt;-f&lt;/span&gt;&lt;span class="pl-k"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;--&lt;/span&gt;followerGatherer    (&lt;span class="pl-k"&gt;Default&lt;/span&gt;: true) Whether the follower gatherer is enabled

  &lt;span class="pl-k"&gt;-f&lt;/span&gt;&lt;span class="pl-k"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;--&lt;/span&gt;GatherData          (&lt;span class="pl-k"&gt;Default&lt;/span&gt;: once per day) How often to gather &lt;span class="pl-k"&gt;data&lt;/span&gt; &lt;span class="pl-k"&gt;in&lt;/span&gt; TimeSpan format

  &lt;span class="pl-k"&gt;-&lt;/span&gt;k&lt;span class="pl-k"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;--&lt;/span&gt;ApiKey              The API key used to connect to the Forem API

  &lt;span class="pl-k"&gt;-&lt;/span&gt;n&lt;span class="pl-k"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;--&lt;/span&gt;NodeList            A list of opensearch nodes

  &lt;span class="pl-k"&gt;-&lt;/span&gt;b&lt;span class="pl-k"&gt;,&lt;/span&gt; &lt;span class="pl-k"&gt;--&lt;/span&gt;BasePath            (&lt;span class="pl-k"&gt;Default&lt;/span&gt;: https:&lt;span class="pl-k"&gt;//&lt;/span&gt;dev.to) The base path of the Forem site

  &lt;span class="pl-k"&gt;--&lt;/span&gt;help                    Display this help screen.

  &lt;span class="pl-k"&gt;--&lt;/span&gt;version                 Display version information.&lt;/pre&gt;

&lt;/div&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jlewis92/ForemAnalyticsGatherer" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h3&gt;
  
  
  Programming language choice
&lt;/h3&gt;

&lt;p&gt;It really wasn't much of a choice in this case as C# is my main language and I do enjoy working with it. Additionally, while .Net 7 is out, after &lt;a href="https://devblogs.microsoft.com/dotnet/net-core-3-0-end-of-life/" rel="noopener noreferrer"&gt;being burnt by .Net Core 3.0 announcing end-of-life a few days before it happened&lt;/a&gt;, I pretty much always work with the long term support version if I can help it, so I went with .Net 6.&lt;/p&gt;

&lt;h3&gt;
  
  
  Forem integration
&lt;/h3&gt;

&lt;p&gt;While I could have written out a full API to talk to the Forem API, this would have taken a long time and I had an OpenAPI spec document, so I used &lt;a href="https://openapi-generator.tech/" rel="noopener noreferrer"&gt;OpenAPI Generator&lt;/a&gt; which can pretty much instantly generate a full API integration, just from the OpenAPI spec.  While I'm using C# and .Net 6, this tool has &lt;a href="https://openapi-generator.tech/docs/generators/" rel="noopener noreferrer"&gt;a pretty large list of supported languages&lt;/a&gt;, so if you wanted to code something similar, you could absolutely use your language of choice, rather than C#.&lt;/p&gt;

&lt;p&gt;While there's a lot of different ways to install OpenAPI Generator, the easiest is probably NPM, where you just need to use the command &lt;code&gt;npm install  @openapitools/openapi-generator-cli -g&lt;/code&gt; and you should be able to start using the generator.&lt;/p&gt;

&lt;p&gt;For me, all I needed to do was run the generator twice, once for V0 and again for V1 using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;openapi-generator-cli&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;generate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OpenAPI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;disc&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;csharp-netcore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--additional-properties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;targetFramework&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;net6.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;apiName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ForemVersion&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;One&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;packageName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ForemVersion&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;One&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;breaking this down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;openapi-generator-cli&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;generate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# generate an API&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nt"&gt;-i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# The location of the Forem API OpenAPI yaml&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;csharp-netcore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# The name of the generator - .net 5+ lives in this one as well&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nt"&gt;-o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# where you want to save the output&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;--additional-properties&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# properties that are generator specific&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;targetFramework&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;net6.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# This where I set .Net 6&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;apiName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ForemVersion&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;One&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# This is the name of the default class generated by the API - it would normally be DefaultAPI&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;packageName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ForemVersion&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;One&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#  This is the name of the C# project that will be generated&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you run this command, go to the output directory, then into src and you should see 2 C# projects:&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%2F18ttplywln1dssgb8o8o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18ttplywln1dssgb8o8o.png" alt="OpenAPI output" width="785" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, it generates 2 C# projects that you can import into a C# solution.  Rather helpfully, the generator &lt;em&gt;also&lt;/em&gt; generates a load of tests for the generated code.  All I needed to do at this point, was import the generated projects into my own via the solution explorer in Visual Studio and I had a ready built integration into Forem:&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%2Fkivmchl71c5cf3s3rqbl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkivmchl71c5cf3s3rqbl.png" alt="solution explorer" width="374" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For those who use C#, the generator uses the RestSharp library by default, as opposed to HttpClient, but it's possible to change this if you really want to.&lt;/p&gt;

&lt;p&gt;There were some bits and pieces I &lt;em&gt;did&lt;/em&gt; need to update in the integration though, as building code this way will do &lt;em&gt;exactly&lt;/em&gt; what swagger definition tells it to do, for example, if a value on a Request or Response is set as &lt;em&gt;Required&lt;/em&gt; and you either don't set the value, or the value comes back as null, the API will throw an error.  When I was doing this, there were a few values in the Articles Response that are marked as required, but I didn't get them back, so did need to modify the generated code very slightly to remove the IsRequired attribute from a few Articles model.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenSearch integration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt; The next few bits are heavily C# based, so if you're not that interested in C# you can probably skip to the point where I start talking about OpenSearch again.&lt;/p&gt;

&lt;p&gt;While ElasticSearch was originally built to ingest log files, you can also use a package to directly integrate OpenSearch into the code itself, which is what I did with this project.  If you're interested in trying this out, you can find a list of supported clients &lt;a href="https://opensearch.org/docs/latest/clients/index/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Integrating OpenSearch into an object-oriented language is extremely simple because all you need to do is pass the objects you want to index into OpenSearch, and then the rest is pretty much handled for you.  For reference, here is pretty much all the code I use to push articles into OpenSearch (after setup):&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;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// Indexes articles into OpenSearch&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;param name="articles"&amp;gt;The articles to index&amp;lt;/param&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;returns&amp;gt;A response based on whether the upload succeeded&amp;lt;/returns&amp;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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BulkResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;IndexArticleData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ForemVersionOne&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArticleIndex&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;articles&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;response&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;_openSearchClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IndexManyAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"articles"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&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;While I could spend some time throwing some additional error handling etc. This code is proof of concept, so I'm not too bothered.  It should also be noted, that the object I'm passing in to this method is taken from the generated API code discussed above.&lt;/p&gt;

&lt;p&gt;Breaking down what I'm doing in OpenSearch is as follows:&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;await&lt;/span&gt; &lt;span class="c1"&gt;// I'm using async methods to push the data&lt;/span&gt;
&lt;span class="n"&gt;_openSearchClient&lt;/span&gt; &lt;span class="c1"&gt;// calling an OpenSearch client I setup in this class&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IndexManyAsync&lt;/span&gt; &lt;span class="c1"&gt;// I'm using index many, inbstead of index as I just want to push in a load of articles, then forget about it&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// The list of articles I want to push into OpenSearch&lt;/span&gt;
&lt;span class="s"&gt;"articles"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// The index in OpenSearch I'm pushing the data to&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Re-indexing articles that I've previously indexed does not cause a new version of the article to be indexed, but does update the old article.  While I've not dug into the OpenSearch code, I'm assuming this means there's something which is tracking identifiers attached to the data being pushed in (like this endpoint does have).&lt;/p&gt;

&lt;h3&gt;
  
  
  Pulling everything together
&lt;/h3&gt;

&lt;p&gt;Now I've got code that can handle both pulling data down from Forem and then taking that data and pushing it into OpenSearch, I now just need to pull everything together and provide an interface that is easy to use.&lt;/p&gt;

&lt;p&gt;For pulling everything together, it's not that special, I'm just using a standard C# library project that takes in an AppSettings object for settings.  I did decide that I wanted the ability to toggle the collection of data from each endpoint, so I did split out the code along these lines.  Also, given the data is paginated, I do loop through until I can get all the data for use.  I understand this is not the most "efficient" method of doing this as I'm retrieving data I've previously indexed, but that might be something I look at in the future.  If you're interested, the code for how this looks, the article's endpoint is &lt;a href="https://github.com/jlewis92/ForemAnalyticsGatherer/blob/main/ForemAnalyticsGatherer/DataGatherers/ArticleData.cs" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Settings
&lt;/h3&gt;

&lt;p&gt;In terms of access, I decided the easiest thing to do was to create a console app project that can link into the analytics library easily and be very flexible. For example, as it's just an executable it can be run directly, via a service or via scheduled task with minimal setup.  This is all facilitated via a timer event being fired off after a set amount of time (default to once a day) so that you don't need to keep running the data collection task.  I also added the ability to run as a Linux docker container because Visual Studio makes this only a couple of button presses to add:&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%2Fv0scxl59p66fzrakutgh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0scxl59p66fzrakutgh.png" alt="Visual Studio docker support" width="660" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to work, the application does need some settings passed in, most notably there is an AppSettings file built into the project.  This controls settings for the ForemGatherer library itself and by default looks like this:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AppSettings"&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;"BasePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://dev.to"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"NodeList"&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="s2"&gt;"http://localhost:9200/"&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;"ApiKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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="err"&gt;While&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;API&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;don't&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;reccomend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;it&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The project is set up to pull settings into this file via several methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;directly into the file - support is also there for using &lt;code&gt;NETCORE_ENVIRONMENT&lt;/code&gt; i.e.: &lt;code&gt;appsettings.dev.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dotnetcoretutorials.com/2022/04/28/using-user-secrets-configuration-in-net/" rel="noopener noreferrer"&gt;user secrets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;command line arguments

&lt;ul&gt;
&lt;li&gt;These override all other methods&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;The command line arguments supported are as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--articleGatherer&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Default:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Whether&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;article&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gatherer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--followerGatherer&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Default:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Whether&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;follower&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gatherer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--GatherData&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Default:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;once&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;per&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;How&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;often&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gather&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TimeSpan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nt"&gt;-k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--ApiKey&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Forem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;API&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nt"&gt;-n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--NodeList&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opensearch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nt"&gt;-b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--BasePath&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Default:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://dev.to&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Forem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nt"&gt;--help&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;Display&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;screen.&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nt"&gt;--version&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="n"&gt;Display&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;information.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I said, I can also run this as a Docker container, and I've got some vague ideas to link it into a docker compose so that I can run everything together:&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%2Fqacngopuypw5qeaynix8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqacngopuypw5qeaynix8.png" alt="Full Docker Desktop" width="749" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All you need to do is run the code and verify that you're getting some data in OpenSearch. You can do this by going to the following address in a web browser &lt;code&gt;http://localhost:9200/_cat/indices?v&lt;/code&gt; and you should see something like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;health status index                           uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   .opensearch-observability       fnli1mGtSJCzRUsMXXQJYg   1   1          0            0       416b           208b
green  open   followers                       2bSUsOgGSBOi3rJyuq3NPw   1   1         52            0     73.6kb         36.8kb
green  open   .opendistro-reports-definitions SqwXfm_BRMmyTk305ktGaQ   1   1          0            0       416b           208b
green  open   articles                        2LNsx-bOS6GizJvoyRAfLQ   1   1          8            0    112.8kb         56.4kb
green  open   .opendistro-reports-instances   40Wf6ofGStSqqJW7Z3-jew   1   1          0            0       416b           208b
green  open   .kibana_1                       bc9P7hL8SJ68jDutyPajFg   1   1         23            9     95.4kb         47.7kb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  OpenSearch Dashboards
&lt;/h2&gt;

&lt;p&gt;Once in OpenSearch, I need to add the data to the dashboard from OpenSearch.  To do this, open the hamburger menu to the left and go to Management &amp;gt; Stack Management &amp;gt; Index Patterns and this should give you the following page:&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%2Fa8afipp9y1lqej0cbez0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa8afipp9y1lqej0cbez0.png" alt="Index Patterns" width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Index Patterns are used to tell OpenSearch Dashboard what data you're going to use.  These can incorporate as many or as few indexes as you want, but given you also need to set a time field, which is different between Followers and Articles, which meant I just created 2 index patterns.  For articles, you do get a few choices on the next screen, which is setting a time field, but I'm most interested in published articles, so that's what I chose.  This has the knock on impact of essentially removing my draft articles, which is also what I wanted to do.&lt;/p&gt;

&lt;p&gt;Now I've got data into the OpenSearch Dashboard, I need to analyse the data. To start with, I used the Discover tool to see if there was interesting, which I did find pretty quickly, such as DEV using &lt;a href="https://cloudinary.com/" rel="noopener noreferrer"&gt;Cloudinary&lt;/a&gt; for image storage and that I've been fairly consistent in releasing articles:&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%2Fyx3wxsh7e3dp2c4yhzyi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyx3wxsh7e3dp2c4yhzyi.png" alt="OpenSearch Discover" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the nicest things about OpenSearch is that I can set how long the data range I want to view is:&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%2Fbhrqxyif0rqurhga1ew0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbhrqxyif0rqurhga1ew0.png" alt="OpenSearch time" width="779" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is pretty helpful, as it lets me set the exact length of time I'm interested in, as opposed to the stats page, where the only option after monthly is infinite which is pretty difficult to see what's going on, each day as I've now been on the sight (slightly) more than a month.&lt;/p&gt;

&lt;p&gt;I'm not going to go through how I made every individual visualization, but if you're interested I've dropped a copy of the dashboard I built in the &lt;a href="https://github.com/jlewis92/ForemAnalyticsGatherer/tree/main/OpenSearch%20dashboard" rel="noopener noreferrer"&gt;repository for this project&lt;/a&gt;. Here are some pictures that I've taken of the dashboard:&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%2Fg5w2tp9d1m2yqltt6gj7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5w2tp9d1m2yqltt6gj7.png" alt="OpenSearch Dashboard" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;It definitely bears repeating, I'm not a data scientist, so I'm sure you could figure out some better things to look at (as well as not constantly changing case in the names of the visualizations).  I do think this does give a pretty good idea of the things I can gather for helping me see how I'm doing on DEV.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;It's pretty clear the API is still heavily under development by the Forem team, evidenced by the "Tags" endpoint disappearing while I was writing this article (to be fair it wasn't THAT useful anyway) and that there's a &lt;a href="https://github.com/forem/forem/pull/19062" rel="noopener noreferrer"&gt;fairly new pull request for removing V0 endpoints&lt;/a&gt; I think it's likely that access to the OpenAPI docs (and followers and articles) will stay around.  I'm thinking I could pull out some more data based around the endpoints I do have, but I do need to do some thinking about.  Also, given the API is under active development, and there's a fair amount of data DEV has access to in the analytics console that I don't have access to via the API, it would be nice to extend my dashboard to include this if it does get updated.&lt;/p&gt;

</description>
      <category>dev</category>
      <category>opensearch</category>
      <category>data</category>
      <category>analytics</category>
    </item>
    <item>
      <title>A developer's guide to estimation</title>
      <dc:creator>Jack Lewis</dc:creator>
      <pubDate>Fri, 17 Mar 2023 16:48:15 +0000</pubDate>
      <link>https://dev.to/jlewis92/a-developers-guide-to-estimation-3143</link>
      <guid>https://dev.to/jlewis92/a-developers-guide-to-estimation-3143</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;I should probably start by saying I &lt;em&gt;really&lt;/em&gt; dislike estimating.  Most of the time it's a bit like licking your finger and sticking it into the air and the everywhere else it's even worse! Between having to (usually) quantify numerous unknowns that can slow you down, you have to try and plan in whether the co-worker helping you out isn't going to (rightly) go on sick leave just before that major dependency comes in. When you finally do give an estimate, to the people you are giving the estimate to, it's not an &lt;em&gt;estimate&lt;/em&gt;, it's a guarantee and when it doesn't happen for a multitude of reasons, you get a pretty uncomfortable meeting where you have to try and explain why you didn't do what you said you would.  This feeling is so entrenched into software development that there's a pretty common 'joke' about it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;person: Hey we've got this really cool feature we need adding! All you need to do is add a button to the form
developer: 3 weeks.
person: but it's just adding a button?
developer: ah sure, it'll take 4 weeks.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;However, none of this really matters, because the business needs this information to try and forecast future work, and if this doesn't happen, the company can go out of business.  So as much as I dislike doing estimates, it's still a key skill for any developer to have.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to estimate
&lt;/h2&gt;

&lt;p&gt;So we've established that we all need to know how to estimate, but there's a slight problem in that &lt;em&gt;how does a developer actually estimate&lt;/em&gt;.  I'm sure most of us with a little experience have just thrown a number out and been reasonably accurate in that prediction without really thinking about it.  This is of course, utterly unhelpful when you're trying to learn how to estimate, and you might read articles such as this one from &lt;a href="https://www.timecamp.com/blog/2022/07/the-complete-guide-on-software-development-time-estimation/" rel="noopener noreferrer"&gt;Time Camp&lt;/a&gt; which go through a fair amount of the individual techniques you can use to estimate.  However, in my opinion, the real answer to this we use a mixture of techniques that can give you an estimate in under 30 seconds (really 10) - any longer, and you're probably overthinking it, or the task is too large, and you need to break it down.  Having said that, here is how I personally estimate tasks.&lt;/p&gt;

&lt;p&gt;Estimation can really be broken in to three parts for me.  Getting an actual estimate, converting how long I think it will take into a realistic estimate and then finally, how to convey that estimate to the business. I've broken this down below.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting the estimate
&lt;/h2&gt;

&lt;p&gt;When I think about how long something is going to take, I'm essentially running through questions like in the below flowchart to try and draw together an accurate estimate:&lt;/p&gt;



&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/jlewis92/embed/JjaMyxP?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; The above isn't everything, just representative of the questions you should be asking yourself before you give an estimate.  For example, if the code I'm writing is using an interesting technology, I'd probably slightly reduce the time, just because I'd put extra work into it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Converting to a real estimate
&lt;/h2&gt;

&lt;p&gt;There's a few methodologies you can use to do this, but I'm going to discuss 2 of the more common ones.&lt;/p&gt;
&lt;h3&gt;
  
  
  The three point estimate
&lt;/h3&gt;

&lt;p&gt;Frankly, I don't use this &lt;em&gt;that&lt;/em&gt; often any more, but when I was starting out and had no idea what to do, doing a three point estimate helps you frame the problem in an easy to digest way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Three point estimation is the &lt;em&gt;endpoint&lt;/em&gt; of estimation i.e.: once you've gone through the other steps in trying to figure out how long the task will take&lt;/p&gt;

&lt;p&gt;While there are &lt;a href="https://en.wikipedia.org/wiki/Three-point_estimation" rel="noopener noreferrer"&gt;several&lt;/a&gt; ways to get to a three point estimation, we're most interested in the quickest way of getting there, so we're going to use the simplest.&lt;/p&gt;

&lt;p&gt;Essentially, you ask yourself these 3 questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How long will it take if it's simpler than I expected&lt;/li&gt;
&lt;li&gt;How long will it take if everything goes wrong&lt;/li&gt;
&lt;li&gt;How long is it going to take realistically, based on what I currently know&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You then divide the 3 estimates by 3 and then dived them by 3.  For example: I get a task to add a button to form and based on what I know, optimistically it will take me an hour to add, realistically it will take 4 hours, and pessimistically it'll take me 2 days.&lt;/p&gt;

&lt;p&gt;This gives me the following:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;1+4+163=7
{1 + 4  + 16 \over 3} = 7
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;4&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;16&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;7&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;Now as this is close to 8 hours, I'd then just round up to a full 8 hour day as that's close enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time plus
&lt;/h3&gt;

&lt;p&gt;This is a really simple way of estimating, and in my experience it's the most popular way of doing this activity.  I &lt;em&gt;really&lt;/em&gt; wouldn't recommend this for a beginner as it's easy to get wrong, but essentially you take how long you think it's going to take, add 20% and then round up to the nearest of 4 hours, 1 day, 1.5 days, 2 days, 3 days, 4 days and 1 week.  Any more and the task should probably be split apart.&lt;/p&gt;

&lt;p&gt;i.e.:&lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;8×1.2=9.6
8 \times 1.2 =  9.6
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;8&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1.2&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;9.6&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;I then take the 9.6 estimate and then round up to 1.5 days.&lt;/p&gt;

&lt;p&gt;This is (usually) far less accurate than a three point estimate, but can get you there most of the time.  In my opinion, despite simpler calculation, this is actually more complicated to do as it relies on already having a full understanding of the solution before you can give an accurate estimate and as I said earlier, better not to rely on if you're new to estimating.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conveying the estimate
&lt;/h2&gt;

&lt;p&gt;Conveying can really be broken down into 2 parts.  What to do when you're giving an estimate, and what to do while you're working on the task.&lt;/p&gt;

&lt;h3&gt;
  
  
  In the meeting
&lt;/h3&gt;

&lt;p&gt;When you're actually in the meeting, and getting asked for an estimate here are some tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Take a few seconds to consider before answering&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Even if you already know what you're going to be asked to do, it always worth spending a few seconds considering the answer in your head as this is the last chance before the estimate is fixed in people's minds.  On multiple occasions has helped me to remember that one little thing I forgot to consider, allowing me to make a more accurate estimate.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Be honest&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;I know that occasionally, you will get pressured to give shorter estimate than you really expect it to take.  If this is happening to you, trust me it's going to be far less awkward to explain now, than when you have to tell the same people pressuring you it's going to take longer than you said&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Be honest with yourself&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;If you're throwing out estimates that you &lt;em&gt;think&lt;/em&gt; may be possible, but have historically taken you a lot longer (as well as constantly having to explain to the project manager why it's taking longer), it's probably a good idea to take a look at you're estimation process.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Don't panic&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;If you're new to the process of giving estimates, and the other people around you know that if you get it wrong it's not such a big deal, provided you're getting closer every time.  We've all been where you, are, and it's not an easy thing to do when you're just getting started&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Explain you're reasoning&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;What I mean by this is, is that just give a short sentence showing you understand the task and set expectations around difficulties you may have.  For example, if I know that I've got dependencies on another team, I'd probably say something like "I can get this done by end of day tomorrow, provided I get dependency X in the next couple of hours".&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&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%2F5iqauxahixf5hvip2x07.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%2F5iqauxahixf5hvip2x07.jpg" alt="Estimation" width="640" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  After the estimate
&lt;/h3&gt;

&lt;p&gt;Once you've given your estimate, there are some things you can do to help try and get the task done on time and also mitigate issues if you do get it wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timeboxing&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;There are a lot of guides on how to this (such as &lt;a href="https://clockify.me/timeboxing" rel="noopener noreferrer"&gt;this one&lt;/a&gt; by Clockify), but in a nutshell it's the practice of allocating a set time in your day when you try and complete the task.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Communicate&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;If you've said a task is going to take a week, and every day you've said everything's going well up until the final day when you suddenly say that you're going to need another week, you are pretty much asking for angry questions to be asked.  It's always better to make sure the project manager understands how you're doing and in my mind, is a major reason for the daily stand-up.  If you're team isn't doing a daily stand-up for some reason, then you probably have access to an instant messaging tool, and you can use that instead.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Don't worry if you get it wrong&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;We've &lt;em&gt;all&lt;/em&gt; been in this position at some point, the best thing to do is to not get too worried over it, and let somebody know that you might need some help and why you're having issues.  What will get you in trouble is not the underestimation, it's when you try to hide it.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>estimation</category>
      <category>tools</category>
      <category>management</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The AWS Cloud Resume Challenge</title>
      <dc:creator>Jack Lewis</dc:creator>
      <pubDate>Fri, 10 Mar 2023 16:15:38 +0000</pubDate>
      <link>https://dev.to/jlewis92/the-aws-cloud-resume-challenge-4p17</link>
      <guid>https://dev.to/jlewis92/the-aws-cloud-resume-challenge-4p17</guid>
      <description>&lt;p&gt;&lt;em&gt;&lt;strong&gt;NOTE:&lt;/strong&gt;&lt;/em&gt; I did write this article a month ago, but waited to release it to see how much it would cost - turns out it was about 60p&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Back in January I'd made a promise to myself to spend at least a little bit of time doing something technical every week, whether that be writing software, building out hardware or just generally reading something to try and keep my skills up.  To start with I was going to try and complete code &lt;a href="https://t2informatik.de/en/smartpedia/code-kata/" rel="noopener noreferrer"&gt;kata's&lt;/a&gt; in a few languages I wasn't too familiar with, such as python.  However, knowing myself I do just find writing code with no reason other than to write code to be not that interesting to me, so I was looking for something a bit different.  As a result I'd been looking around the internet for something technical to do and came across the &lt;a href="https://www.awspuritytest.com/" rel="noopener noreferrer"&gt;AWS purity test&lt;/a&gt; which I did as a bit of fun as I work with AWS pretty heavily in my day job.  One of the questions on the test was talking about completion of the &lt;a href="https://cloudresumechallenge.dev/" rel="noopener noreferrer"&gt;cloud resume challenge&lt;/a&gt; and while I'm not in the market for a new job, it sounded like a really interesting project to tie all my skills together and I thought might as well try it.  3 weeks later I've got the following:&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%2Fg76q408he24ufmidaa7h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg76q408he24ufmidaa7h.png" alt="Resume Challenge" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Impressive right? No not really, but to be fair it's a resume so it's supposed to be simple and most of the interesting stuff in this project is pretty much all behind the scenes.&lt;/p&gt;

&lt;p&gt;If you're interested in viewing the website itself it's &lt;a href="https://resume.jacklewis.dev/" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Slightly more impressive, I think is this diagram of the end product:&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%2Fubqkob6bep8q4bm9v44b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubqkob6bep8q4bm9v44b.png" alt="cloud challenge" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the challenge?
&lt;/h2&gt;

&lt;p&gt;Put simply, it's a series of 16 steps to build and host a static website in your cloud of choice, then pull some data in from a database.  You also need set up a fully automated deployment of both the backend and frontend using infrastructure as code.  Once you've done all this, you then need to post a write-up about it somewhere online with tips and tricks and your experiences of doing the challenge. Additionally, there's a discord you can use if you have questions, but I intentionally avoided this as I wanted to see if I could do this on my own.  If you're thinking of doing the challenge, it might be best to stop here if you want to avoid some spoilers, otherwise let's go through it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Final outcome
&lt;/h2&gt;

&lt;p&gt;Thought I'd put this at the top so it doesn't get lost, but there are essentially 4 outcomes for this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://resume.jacklewis.dev/" rel="noopener noreferrer"&gt;Resume Project website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jlewis92/ResumeProject-frontend" rel="noopener noreferrer"&gt;Frontend code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jlewis92/ResumeProject-backend" rel="noopener noreferrer"&gt;Backend code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jlewis92/DevToArticleRepo" rel="noopener noreferrer"&gt;Dev.to article repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Timeline
&lt;/h2&gt;

&lt;p&gt;This project was completed on a vague timescale of the below.  I wasn't really working on this full time at the start, just a few hours a week but I did a fair amount the last week of this project&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;week 1: HTML/CSS and cloud front hosting&lt;/li&gt;
&lt;li&gt;week 2: JavaScript, database, API and python&lt;/li&gt;
&lt;li&gt;week 3: everything else&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dev environment
&lt;/h2&gt;

&lt;p&gt;I pretty much used vscode to write everything as I've used it pretty heavily previously.  In terms of some of the extensions I'm using they are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer" rel="noopener noreferrer"&gt;Live Server&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Allows you to quickly run and host a simple HTML website and reloads on changes automatically&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Certification
&lt;/h2&gt;

&lt;p&gt;The guide recommends you have a &lt;a href="https://aws.amazon.com/certification/certified-cloud-practitioner/" rel="noopener noreferrer"&gt;cloud practitioner's certification&lt;/a&gt; before starting the challenge.  I don't personally have one of these, mainly because I've been working with AWS in industry anyway so most of the things it's talking about I have a vague idea on anyway.  However, if you're coming into this with no experience whatsoever it's probably a decent way of understanding how AWS works without fumbling blindly&lt;/p&gt;

&lt;h2&gt;
  
  
  HTML
&lt;/h2&gt;

&lt;p&gt;It should be said that the frontend wasn't really the focus of this project so there's not really much to talk about here, I just wrote a simple template in HTML which I could use to put data in.  Given this was supposed to be simple, I'm just directly writing text into the file but if I was actually doing this in a production environment that would definitely not be the case.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS
&lt;/h2&gt;

&lt;p&gt;It has been a fair few years since I've written purely in HTML/CSS without something like bootstrap or tailwind to make life easier for me, but I think I did an ok job, some of the slightly more interesting things I did were using media queries to set styling based on screen width so it would be a bit more useful for mobiles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt; &lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;screen&lt;/span&gt; &lt;span class="n"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000px&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;.resume-body-skills-wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;column-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;250px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&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;This then does the following with the skills section on the resume:&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%2Flxlkh73bv4rxutcpey5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flxlkh73bv4rxutcpey5j.png" alt="Large screen" width="678" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;One of the interesting thing's I did find was that you can use css to do stuff when certain tags are sat next to each other, like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c"&gt;/* adds in 50 px spacing when there's a double span tag */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Both
&lt;/h3&gt;

&lt;p&gt;Generally, I've worked much more heavily WPF when building frontend's which uses XAML.  I think as an observation between the 2, I've found that it's probably easier to place your content where you want it to go with XAML (I tend to not find my self googling how to centre content anyway!) but actually styling elements within XAML is much harder.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS setup
&lt;/h2&gt;

&lt;p&gt;If you've never setup an AWS account before, it's pretty straight forward, just go &lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;here&lt;/a&gt; and give AWS your credit card number.  While outside the scope of this article, it's pretty much mandatory for me that you complete all of the security recommendations in IAM before starting as well as enabling MFA everywhere.  You should also download the AWS CLI and setup credentials using aws configure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;aws&lt;/span&gt; &lt;span class="n"&gt;configure&lt;/span&gt;
&lt;span class="n"&gt;AWS&lt;/span&gt; &lt;span class="n"&gt;Access&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;
&lt;span class="n"&gt;AWS&lt;/span&gt; &lt;span class="n"&gt;Secret&lt;/span&gt; &lt;span class="n"&gt;Access&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;access&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;
&lt;span class="n"&gt;Default&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;
&lt;span class="n"&gt;Default&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've also setup MFA everywhere you might also need to use the &lt;code&gt;sts get-session-token&lt;/code&gt; CLI function as well.  AWS recommends you use the toolkit instead but I've tended to find it didn't work very well when I was using it for work last year.  You should also setup billing alerts on your account, because &lt;a href="https://www.reddit.com/r/aws/comments/lbqcos/my_forgotten_account_has_a_20000_bill_how_screwed/" rel="noopener noreferrer"&gt;unexpected bills are terrible&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Static Website
&lt;/h2&gt;

&lt;p&gt;This is really easy to do, you just go to S3 give it a name and ignore all the warnings it gives you when you untick block all public access:&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%2F2c3t848c268jiiojwowr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2c3t848c268jiiojwowr.png" alt="scary warnings" width="745" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point, all you need to do is upload your website files, then go to properties -&amp;gt; static website permissions and tell it what the index document of your website is.  At this point, all you need to do is select the link S3 gives you and you should see your website on a random http url.  At this point you're done! Well, not really, if all you wanted to do was host your resume, that's it unfortunately we're not even halfway through if you want to complete the actual challenge. &lt;a href="https://aws.amazon.com/s3/pricing/" rel="noopener noreferrer"&gt;S3 pricing&lt;/a&gt; is pretty negligible for this project as I'm storing under 1MB and AWS charges about 2 cents per GB per month&lt;/p&gt;

&lt;h2&gt;
  
  
  DNS
&lt;/h2&gt;

&lt;p&gt;Technically, HTTPS comes before this step, but CloudFront offers a lot of automation if you have this up beforehand.  The guide recommends using using Route 53 but I'm not a massive fan as the interface is pretty old school, and even though I've set my account into GBP, it's still showing me prices in dollars. Finally, it also doesn't have the .dev top level domain that I want, possibly because Google operates it:&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%2Fphc4mv6ui2lmigrwe3ql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fphc4mv6ui2lmigrwe3ql.png" alt="no .dev" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I specifically wanted .dev as it matches what I do and also comes with added bonus of forcing HTTPS, meaning if I want this to work it needs to have certificates setup properly. For this domain to buy it off &lt;a href="https://domains.google.com/registrar/search" rel="noopener noreferrer"&gt;Google Domains&lt;/a&gt; and set it to auto renew&lt;/p&gt;

&lt;p&gt;Only real change to this is that I had to use google domains and then &lt;a href="https://www.entechlog.com/blog/aws/connect-google-domain-to-aws-route-53/" rel="noopener noreferrer"&gt;transfer&lt;/a&gt; it to Route 53.  This is the point where you're going to incur a small monthly fee as &lt;a href="https://aws.amazon.com/route53/pricing/" rel="noopener noreferrer"&gt;pricing for Route 53&lt;/a&gt; charges 50p per month per hosted zone, then a further 40p for every 1 million requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTPS
&lt;/h2&gt;

&lt;p&gt;Really, this section should be called the CloudFront section of the guide as it's basically what you're going to do here.  I wasn't a massive fan of this section as pretty much the website link that the AWS resume challenge for this basically does everything for you and when I'm trying to learn, I'd much prefer this to be a bit more separated out.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudFormation
&lt;/h3&gt;

&lt;p&gt;To start with , you're pretty much going to go to the &lt;a href="https://aws.amazon.com/blogs/networking-and-content-delivery/amazon-s3-amazon-cloudfront-a-match-made-in-the-cloud/" rel="noopener noreferrer"&gt;link&lt;/a&gt; in the AWS resume challenge and then choose stack 3 and filling out the questions it asks you.  This wasn't a great experience for figuring out how it works as the CloudFormation template is pretty obtuse, given it calls out to other scripts within it, and the CloudFormation designer UI is pretty cluttered and difficult to see what's going on:&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%2Fuxg9jxe2q5vqu5mzxi88.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuxg9jxe2q5vqu5mzxi88.png" alt="CloudFormation" width="654" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the entire section of the template showing how the ACM stuff works is just being pulled in from another template. Also, if you drag the designer around, your location in the template gets reset to the top which is really annoying.  However, I did like that the template made it super easy to set the subdomain to "resume" instead of "www" as I'm planning to host a more modern website on that subdomain at some point in the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudFront
&lt;/h3&gt;

&lt;p&gt;Once you've gotten you're template done and waited the 20 minutes for everything to get setup for you, all you need to do is throw your website into the bucket the template generated and watch as your site is served! Well, with a caveat - because CloudFront is using edge locations to serve your content as close to the geographical location of the caller as it can, it basically has to distribute these to  the servers around the world and cache them.  As a result, it can take up to 2 days for content to be displayed throughout the world.  Once you've realised this, you can take advantage of CloudFront &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html" rel="noopener noreferrer"&gt;invalidation requests&lt;/a&gt; to force the cache to refresh.  The &lt;a href="https://aws.amazon.com/cloudfront/pricing/" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; for this is that you get 1000 free invalidation requests per month, and if you want to refresh the entire website you can use &lt;code&gt;/*&lt;/code&gt; or for individual files you can use &lt;code&gt;/&amp;lt;file name&amp;gt;.&amp;lt;file name type&amp;gt;&lt;/code&gt; and list the specific ones you want.  You also need to pay for edge computing, but currently my bill is sat at £0.&lt;/p&gt;

&lt;p&gt;As a sidenote, the CloudFormation template does generate logs into an S3 bucket without rotation.  For now I've left them on as it's pretty small amounts of data, but I've still set a lifecycle retention policy on the logs folder just in case.  You can also set alerts around stuff like number alerts over a time period or data the rate for upload and download among other things.  You can also get some metrics around the website, such as usage:&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%2Fj9faqpw1bgori9clatyb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj9faqpw1bgori9clatyb.png" alt="extremely high usage" width="769" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or viewers:&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%2Fzxjt6ufgcfmnbnprx8qg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzxjt6ufgcfmnbnprx8qg.png" alt="viewers" width="494" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;NOTE:&lt;/strong&gt;&lt;/em&gt; one of the big errors I had around the CloudFormation template was around how it basically disables JavaScript from running by setting the Content Security Policy to deny everything in the AWS Role that it generates, as I'd missed this when I first went through it, it caused me a lot issues later on.&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript
&lt;/h2&gt;

&lt;p&gt;This part of the challenge is to create a visitor counter that's updated via JavaScript&lt;/p&gt;

&lt;p&gt;I think this is the first time I've worked fully with vanilla JavaScript before as I normally use TypeScript or some sort of framework.  However, there's really not much to speak about on it as it's less than 10 lines long. All it's doing is getting some text from an API and then setting a visitor counter on the footer to that value.  I can say I much prefer using frameworks which let me set values using &lt;code&gt;{braces}&lt;/code&gt; rather than having to use the document query selector.&lt;/p&gt;

&lt;h2&gt;
  
  
  DynamoDb
&lt;/h2&gt;

&lt;p&gt;I've used dynamo pretty heavily before so I'm pretty much using this to brush up on this again.  For myself, while you can write tables straight into AWS, I find it's much easier to visualise and work on tables using an offline tool called &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/workbench.html" rel="noopener noreferrer"&gt;NoSql Workbench&lt;/a&gt;.  If you want to use DynamoDb offline is to just set the toggle for DDB Local server and publish a table into a local connection and it's now setup for you:&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%2Fep65x4qu4g5ji1bq5hhz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fep65x4qu4g5ji1bq5hhz.png" alt="DDB Local" width="337" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once this is setup, all you need to do in order to use DynamoDb local is attach an endpoint url when you create a client for DynamoDb.  If you haven't changed the default port for DynamoDb, it will look something like this in Python - &lt;code&gt;endpoint_url="http://localhost:8000"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, the best thing about NoSql Workbench is it's ability to do your work for you by generating code directly using the operation builder:&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%2Fkl2vrq4k7ogtzd0cm50c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkl2vrq4k7ogtzd0cm50c.png" alt="code generation" width="800" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The expression above basically increments the VisitorCount column by 1 and if I hit the generate code button, it basically gives me the code to do this in Python, JavaScript or Java.&lt;/p&gt;

&lt;p&gt;I quite enjoy working with DynamoDb and always have done as I find it's really quick to get going in development.  There is a fairly large learning curve if you're coming SQL databases as the single table design and replication of data can be a little mind-bending.  There are some pitfalls to be aware of, for example retrieving ALL the data out of a table is considered a bad idea because of how much it can cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambda
&lt;/h2&gt;

&lt;p&gt;This section comes after the API in the cloud resume challenge document, but I find it's usually easier to write the code that retrieves data from the database, before you build out the API calls.  In this case, I'm writing an AWS lambda expression in Python.  I'm using Python as I've previously written lambda expressions in C# and I thought the best thing to do for this point was to work in a language I've only had a little experience of.&lt;/p&gt;

&lt;p&gt;I did spend more time than I was expecting on this section of the project though...&lt;/p&gt;

&lt;p&gt;I'm pretty happy with Python as a language as it's easy to see what's going and at least in small scripts the lack of compilation speeds up development.  However, I think I do still prefer C# to write Lambda expressions in and just general development.  This is almost entirely due to not the language itself and more the tooling surrounding the language in Windows (I'm aware this is different in Linux) as I'm feeling like I'm constantly fighting it during initial setup.  For example, while sure nowadays if you type Python into PowerShell, it just opens up a Windows store window which allows you to get hold of Pip and Python quickly, what it doesn't do, is setup the scripts folder on your PATH, which led to issues when I was using the &lt;a href="https://pypi.org/project/python-lambda-local/" rel="noopener noreferrer"&gt;Python lambda local&lt;/a&gt; library to test the AWS lambda expressions. Then of course there's messing around with venvs in vscode.&lt;/p&gt;

&lt;p&gt;However, I did manage to eventually get everything up and running, using some slightly modified code from NoSql workbench which would allow it to return the data in the same call to update the database. I think the code is pretty good as it runs in under a second on AWS Lambda.  Given lambda &lt;a href="https://aws.amazon.com/lambda/pricing/" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; gives you over 800 hours free per month at 128Mb, I think this is fine.&lt;/p&gt;

&lt;p&gt;Here's the full request for updating and returning a value for the visitor counter in DynamoDb:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_update_item_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TableName&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;S&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;resumeProject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UpdateExpression&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SET #visitorCount = #visitorCount + :increment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ExpressionAttributeNames&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#visitorCount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VisitorCount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ExpressionAttributeValues&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:increment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;N&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ReturnValues&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UPDATED_NEW&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want, here's a direct &lt;a href="https://github.com/jlewis92/ResumeProject-backend/blob/main/python/UpdateVisitorCount.py" rel="noopener noreferrer"&gt;link to the full source code&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests
&lt;/h2&gt;

&lt;p&gt;Once I got this all done I moved onto writing some tests.  I just decided to keep simple and use the &lt;code&gt;unittest&lt;/code&gt; library that's built into Python.  I then found that there's a pretty great mocking library for the AWS boto3 client called &lt;a href="https://pypi.org/project/moto/" rel="noopener noreferrer"&gt;moto&lt;/a&gt; that can be used to write proper tests for Python.  Compared to C# this is great, as my only option there appears to be writing wrapper classes that call the function.  To be honest though, this is where I spent a lot of my time as I ended encountering a bug in moto that I ended up making an &lt;a href="https://github.com/getmoto/moto/issues/5916" rel="noopener noreferrer"&gt;issue&lt;/a&gt; on GitHub about.  Fortunately, the people running the moto repository responded quickly and it's been fixed - all I need to do now is update my tests to work with the fixes!&lt;/p&gt;

&lt;h2&gt;
  
  
  API
&lt;/h2&gt;

&lt;p&gt;The API is built using API gateway that has a lambda proxy integration to the lambda expression I'd built in the previous step and returns data to the client like this:&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%2Fozalozqzek2yf5d2q3e7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fozalozqzek2yf5d2q3e7.png" alt="gateway response" width="243" height="109"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I decided to just return a string because it was easy that way.  If I was to extend this and have more than a single value returned to the client I'd change this to JSON as it's much nicer handing data between server and client.  However, this was such a small part of the project I just decided not to do it. I understand it's not a great idea though as my website just returns whatever the result of the fetch is, I'm just lazy and had spent far too much time on this already.  I should also say I'm a bit iffy about leaving a lambda gateway open as I usually stick them behind a Cognito pool or oauth authoriser whenever I've used an API from API gateway in the past.&lt;/p&gt;

&lt;p&gt;Just as a sidenote however, if you're doing a lambda proxy in the gateway, the response returned by the lambda must be in the format as follows:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"headers"&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;Once I got this all hooked up, I just updated the JavaScript to pull in from the backend and I can now see my visitor counter is updating:&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%2F8va2ak6p5cdkjvdk8jhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8va2ak6p5cdkjvdk8jhj.png" alt="visitor counter" width="391" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although no it wasn't initially, as I needed add CORS headers to the request to get it to display content.  Technically, I could have just hosted it on an endpoint within jacklewis.dev, but I wanted to try adding headers directly instead. I'll talk about how I did that in the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure as Code
&lt;/h2&gt;

&lt;p&gt;For this I used Terraform for the backend as I've used it a little bit before.  It's a pretty great tool for describing resources in AWS and has the benefit of being cross-platform as opposed to the AWS SAM model first pointed out the the cloud resume challenge website.  I should probably note as well, that most of the stuff I did build directly via the console to check my understanding, before translating it into Terraform. I like to style Terraform by having not much in the &lt;code&gt;main.tf&lt;/code&gt; file and then linking each file to it's purpose.  As a result the structure for the Terraform ended up like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;├── api-gateway.tf
├── dynamo.tf
├── github-runner.tf
├── iam.tf
├── lambda.tf
├── main.tf
└── provider.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Terraform is setup to use variables for everything which I don't people to have access to, such as my account id.  These values are injected into the Terraform .env file that I get to run locally using the &lt;a href="https://marketplace.visualstudio.com/items?itemName=hzeineddine.terminal-dotenv" rel="noopener noreferrer"&gt;Terminal.env&lt;/a&gt; extension to make it easier to pull these values in when I want to run the Terraform.&lt;/p&gt;

&lt;p&gt;I'm going to talk through each of these files individually (outside of main and provider) as they all have interesting features&lt;/p&gt;

&lt;h3&gt;
  
  
  API Gateway
&lt;/h3&gt;

&lt;p&gt;Within this &lt;code&gt;.tf&lt;/code&gt; file I've got configuration entirely related to the single endpoint I've got in my gateway.  However, there is actually 2 endpoints i the repository, a POST which actually returns the data and an OPTIONS request which is used for CORS.  Unfortunately, the way to write Terraform for gateway is pretty verbose as there's quite a lot of steps to enable everything together.  I've split it all out though with headers to make it a little easier what each step is - for reference, it's basically having to setup all of these stages:&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%2Fl6zhiac0hr5er8ozq0l5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6zhiac0hr5er8ozq0l5.png" alt="gateway response" width="800" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This particular request is taken from an example &lt;a href="https://codeburst.io/aws-api-gateway-by-example-3733d7792635" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you want to see how this was setup within the AWS console.&lt;/p&gt;

&lt;h3&gt;
  
  
  DynamoDb Terraform
&lt;/h3&gt;

&lt;p&gt;The interesting part for this is that I was using Terraform import commands to import existing resources in AWS into Terraform, so I don't need to recreate them and then delete the old one. Otherwise, the setup for this is pretty straightforward&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Runner Terraform
&lt;/h3&gt;

&lt;p&gt;This was interesting in that AWS and GitHub have recently integrated the ability to use &lt;a href="https://www.pingidentity.com/en/resources/identity-fundamentals/authentication-authorization-standards/openid-connect.html#:~:text=OpenID%20Connect%20(OIDC)%20is%20an,network%2C%20to%20authenticate%20their%20identities." rel="noopener noreferrer"&gt;OIDC&lt;/a&gt; to run build jobs, which is a lot safer than just setting a secret access token as a secret on GitHub and hoping nobody steals it in the several years you don't touch it. How this works is that GitHub and AWS negotiate for a short-lived access token that can run stuff in your account via an AWS role you define.  I did purposely leave out defining a role for this account as I wanted to control access more directly via the AWS console as it's quite important.&lt;/p&gt;

&lt;h3&gt;
  
  
  IAM Terraform
&lt;/h3&gt;

&lt;p&gt;This is a pretty standard (and small) section which basically gives the AWS lambda expression access to the DynamoDb table and write logs about the output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda Terraform
&lt;/h3&gt;

&lt;p&gt;I've previously has lambda expressions written which are uploaded to S3 because keep track of versions was important, in this implementation though, I've got it to zip up and directly upload into a lambda expression.  However, what can do is set a source code hash like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="nx"&gt;source_code_hash&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;archive_file&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zip_the_python_code&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output_base64sha256&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which means that the lambda will only be uploaded if there's been actual changes to the file.  I've also set a retention policy for AWS CloudWatch as it doesn't have this by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overall
&lt;/h3&gt;

&lt;p&gt;I did quite enjoy writing the Terraform, it's interesting to work on something like this and really, there's nothing quite like just entering &lt;code&gt;terraform apply -auto-approve&lt;/code&gt; and just watching everything get update for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI-CD Backend
&lt;/h2&gt;

&lt;p&gt;The backend can be found &lt;a href="https://github.com/jlewis92/ResumeProject-backend" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So to start with I just setup a private GitHub repository (that I've since made public) and stick a Terraform and Python .gitignore file into it and the MIT licence.  After that I added some Git &lt;a href="https://pre-commit.com/" rel="noopener noreferrer"&gt;pre-commits&lt;/a&gt; to do stuff like auto-format the Terraform scripts and check if I've left any secrets in plain-text on the repository.  I did get a false positive on the GitHub thumbprint for the OIDC token which is why it has &lt;code&gt;# pragma: allowlist secret&lt;/code&gt; written next to it.&lt;/p&gt;

&lt;p&gt;Once I'd done this I committed everything I had and it was time to work on the GitHub Actions project.  As the Terraform is using a load environment variables to run, I set this up as secrets on Actions so that they couldn't be seen in logs:&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%2Fdygqnh9pkf3r7mtwl5wg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdygqnh9pkf3r7mtwl5wg.png" alt="backend secrets" width="787" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After this I wrote a pipeline which does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install and cache dependencies&lt;/li&gt;
&lt;li&gt;Checkout the code&lt;/li&gt;
&lt;li&gt;Run the python tests&lt;/li&gt;
&lt;li&gt;Setup the OIDC tokens for GitHub&lt;/li&gt;
&lt;li&gt;Run a bunch of Terraform formatting tools and checks to make sure everything's good&lt;/li&gt;
&lt;li&gt;Apply the changes if the build is running on the main branch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also got the build to output some nice content about Terraform in a pull request:&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%2F2fs12awds8ma3tuf4qjh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2fs12awds8ma3tuf4qjh.png" alt="Terraform output" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I should probably say, I work on builds pretty regularly so this stuff isn't that new to me&lt;/p&gt;

&lt;h2&gt;
  
  
  CI-CD Frontend
&lt;/h2&gt;

&lt;p&gt;The frontend can be found &lt;a href="https://github.com/jlewis92/ResumeProject-frontend" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The CI-CD for the frontend was simpler than the backend as it didn't have a load of Terraform to run, and it really just needs to sync with an S3 bucket and run an invalidation to force the changes to propagate.  Given there's a limit to the number of invalidation requests you can do before being charged, I did think about adding a rolling monthly limit, but ultimately decided there wasn't much point as I've used less than 20 of them out 1000 I get free every month.  I also added secrets needed by the github runner to get an OIDC token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blog post
&lt;/h2&gt;

&lt;p&gt;Well, this is what you're reading (if you managed to get to the end of my apparent essay on this topic)&lt;/p&gt;

&lt;p&gt;I did decide though, that I'd also automate my ability to add blog posts to dev.to using GitHub.  I decided to be meta about it and published a blog post about how I did this &lt;a href="https://dev.to/jlewis92/my-setup-for-publishing-to-devto-using-github-1k0n"&gt;here&lt;/a&gt; for practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I'm probably going to take a short break from this, but I did really enjoy it so I likely do more of this.  Interestingly, one of my next projects has just arrived so I'll probably working on that soon -  think it's good idea to write a post about it when I finish, as writing these articles was a good way to fix it into my mind.  I've also got a few other more further into the future projects - I'd like to write a homepage which is more reactive (pun possibly intended?) that I'd like to do at some point.&lt;/p&gt;

&lt;p&gt;In terms of this project it's pretty much done just need to keep everything updated, write the proper Python tests with the updated library and likely run through and edit these posts.&lt;/p&gt;

</description>
      <category>challenge</category>
      <category>aws</category>
      <category>github</category>
      <category>terraform</category>
    </item>
  </channel>
</rss>
