<?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: Mary 🇪🇺 🇷🇴 🇫🇷</title>
    <description>The latest articles on DEV Community by Mary 🇪🇺 🇷🇴 🇫🇷 (@pykpyky).</description>
    <link>https://dev.to/pykpyky</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%2F554630%2Fae1b6d2e-edab-4443-9fca-cc4278e6e307.jpg</url>
      <title>DEV Community: Mary 🇪🇺 🇷🇴 🇫🇷</title>
      <link>https://dev.to/pykpyky</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pykpyky"/>
    <language>en</language>
    <item>
      <title>Understood and implement the scoring algo BM 25</title>
      <dc:creator>Mary 🇪🇺 🇷🇴 🇫🇷</dc:creator>
      <pubDate>Mon, 23 Jun 2025 19:16:11 +0000</pubDate>
      <link>https://dev.to/pykpyky/understood-and-implement-the-scoring-algo-bm-25-1gma</link>
      <guid>https://dev.to/pykpyky/understood-and-implement-the-scoring-algo-bm-25-1gma</guid>
      <description>&lt;p&gt;As an SRE, I often have to look for information in logs—a lot of logs, a ton of information, across many files. And generally, whether you're on the internet or just on your own computer, you're facing the same problem: you need to find information buried in an ever-growing pile of documents.&lt;/p&gt;

&lt;p&gt;That brings us to a key problem: when I search for the word &lt;em&gt;Panda&lt;/em&gt; on my computer, which file should come up first? The most recent one? No—the most relevant one! And figuring out whether a document is relevant to a search query is today’s topic.&lt;/p&gt;

&lt;p&gt;Grab a seat, get yourself a coffee, tea, or hot chocolate. Let’s go!&lt;/p&gt;

&lt;p&gt;Today's problem can be summarized like this: for any search, I want to assign a &lt;em&gt;relevance score&lt;/em&gt; to my documents. The first document returned should be the one with the highest score!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Before we begin: a quick note. This article contains some mathematical formulas. Don’t worry—we’ll go through everything step by step, and I’ll explain things clearly so they’re easy to understand.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We'll start with a small list of documents that will guide us through this discussion.&lt;/p&gt;

&lt;p&gt;Here they are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Doc 1&lt;/strong&gt;: “A panda is a black and white animal”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doc 2&lt;/strong&gt;: “The dog is white”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doc 3&lt;/strong&gt;: “The cat is black”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doc 4&lt;/strong&gt;: “The panda is neither a cat nor a dog”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doc 5&lt;/strong&gt;: “The red panda is red”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doc 6&lt;/strong&gt;:  “Black is black, there's really no more hope
I’m in the dark, I find it hard to believe
Black is black, it's never too late
Black is black, I still have hope
Black is black, I still have hope
Black is black, I still have hope”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a simple test set, but it already helps us see the problem with scoring.&lt;/p&gt;

&lt;p&gt;Let’s start intuitively: if I search for the word &lt;em&gt;“black”&lt;/em&gt;, which document is the most relevant? Among documents 1, 3, and 6?&lt;/p&gt;

&lt;p&gt;Not easy, right?&lt;/p&gt;

&lt;p&gt;Before We Go Further: Some Definitions&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our documents—and even our search queries—aren’t made of “words,” but what we’ll refer to as &lt;em&gt;tokens&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In this blog post, a word = a token. So nothing really changes, but since we’ll use the term &lt;em&gt;token&lt;/em&gt; throughout the article, better clarify it now.&lt;/p&gt;

&lt;p&gt;For example, document 1 contains the following tokens:&lt;br&gt;
&lt;code&gt;“A”, “panda”, “is”, “a”, “black”, “and”, “white”&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inverted Index&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When we search for the word &lt;em&gt;black&lt;/em&gt; in our documents, we don’t open them one by one to see if the word is inside.&lt;/p&gt;

&lt;p&gt;Before any search happens, our application must index the documents—scan through them to extract the tokens in each. During this step, we build what’s called an &lt;em&gt;inverted index&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;An inverted index is a dictionary where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The key is a token&lt;/li&gt;
&lt;li&gt;The value is a list of documents that contain that token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, our inverted index might include keys like:&lt;br&gt;
&lt;code&gt;“the”, “panda”, “is”, “a”, “animal”, “black”, “and”, “white”, “dog”, “cat”, …&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
The key &lt;em&gt;“panda”&lt;/em&gt; is associated with documents 1, 4, and 5.&lt;br&gt;
The key &lt;em&gt;“black”&lt;/em&gt; is associated with documents 1, 3, and 6.&lt;br&gt;
And so on.&lt;/p&gt;

&lt;p&gt;Here’s a quick look at the classes we’ll be working with.&lt;/p&gt;

&lt;p&gt;First, the &lt;code&gt;Document&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Document&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="n"&gt;Id&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="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="n"&gt;Path&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="n"&gt;Content&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;int&lt;/span&gt; &lt;span class="n"&gt;Length&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ContentTokens&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TitleTokens&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;DateTime&lt;/span&gt; &lt;span class="n"&gt;IndexedAt&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="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&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;And the search result, via the &lt;code&gt;SearchResult&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchResult&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="n"&gt;Id&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="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="n"&gt;Path&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="n"&gt;ContentSnippet&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;double&lt;/span&gt; &lt;span class="n"&gt;Score&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;DateTime&lt;/span&gt; &lt;span class="n"&gt;IndexedAt&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;Our final output will be a list of &lt;code&gt;SearchResult&lt;/code&gt; objects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scoring Algorithm – Simple and Basic
&lt;/h2&gt;

&lt;p&gt;A basic first approach is to count how many times a token appears in the document.&lt;br&gt;
If I search for the token “black”, it appears once in doc1, once in doc3, and 11 times in doc6.&lt;/p&gt;

&lt;p&gt;Let’s assign a score of 1 for each occurrence of the token. This is very easy to do in C# with 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="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;GetScore&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;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&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;score&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;var&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&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="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&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;score&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;Here, we’re counting how many times the token from our query appears in the document.&lt;/p&gt;

&lt;p&gt;So we’ll get a score of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 for documents 1 and 3&lt;/li&gt;
&lt;li&gt;11 for document 6&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alright… But beyond the fact that token count may not be the best metric, we quickly hit another issue.&lt;/p&gt;

&lt;p&gt;If my query is “cat”, “black”, then doc6 will still score 11, even though doc3— which contains both tokens—only scores 2.&lt;/p&gt;

&lt;p&gt;And doc3 actually contains both query tokens! So intuitively, it should be considered more relevant!&lt;/p&gt;

&lt;p&gt;To fix this first issue, we could add a boost to the score if the document contains all the tokens in the query.&lt;br&gt;
But more importantly, we might want to reduce the impact of a token that appears many times in the same document.&lt;/p&gt;

&lt;p&gt;Should a document that contains the token “black” 50 times have a score twice as high as one that contains it 25 times?&lt;br&gt;
Probably not. It should be ahead, yes—but not by that much!&lt;/p&gt;

&lt;p&gt;Also, documents 1 and 3 both have a score of 1 for the token &lt;em&gt;“black”&lt;/em&gt;, and there's no way to break the tie with this simple method.&lt;/p&gt;
&lt;h2&gt;
  
  
  An Improved Version of the Scoring Function
&lt;/h2&gt;

&lt;p&gt;We can modify our scoring function to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calculate the score for all query tokens&lt;/li&gt;
&lt;li&gt;Limit the impact a single token can have on the final score&lt;/li&gt;
&lt;li&gt;Boost results that match multiple tokens
&lt;/li&gt;
&lt;/ul&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;int&lt;/span&gt; &lt;span class="nf"&gt;GetScore&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;queryTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&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;totalScore&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;int&lt;/span&gt; &lt;span class="n"&gt;matchToken&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="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;token&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;queryTokens&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;score&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&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="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&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;score&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;score&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;&amp;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;matchToken&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;

        &lt;span class="n"&gt;totalScore&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;totalScore&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;matchToken&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;totalScore&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;From now on, a token can no longer influence the score beyond 5.&lt;br&gt;
And the document score gains 10 points per token found in the query.&lt;/p&gt;

&lt;p&gt;Let’s do a new test with the query “cat”, “black”:&lt;br&gt;
Matching documents are: 1, 3, 4, and 6.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documents 1 and 4, which each contain only one token, will have a score of 11: 1 point for token frequency, and 10 points for matching a token in the query.&lt;/li&gt;
&lt;li&gt;Document 3 will have a score of 22: 2 points for the presence of “cat” and “black”, and 20 points for matching 2 tokens in the query.&lt;/li&gt;
&lt;li&gt;Document 6 will have 15 points: 5 points for the frequency of the token “black” (capped at 5), and 10 points for matching 1 token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Final result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Doc 3 → 22 points&lt;/li&gt;
&lt;li&gt;Doc 6 → 15 points&lt;/li&gt;
&lt;li&gt;Doc 1 and Doc 4 → 11 points&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ve solved our first two problems… but we can do better!&lt;/p&gt;

&lt;p&gt;Here, the real issue is the limit of 5.&lt;br&gt;
It’s not so much the value “5” that’s problematic, but the fact that we have a linear score increase that suddenly stops at an arbitrary limit.&lt;/p&gt;


&lt;h2&gt;
  
  
  Diminishing Returns
&lt;/h2&gt;

&lt;p&gt;To solve this problem, we won’t impose a fixed limit beyond which the frequency of a term no longer yields points, but instead apply diminishing returns. For example, the first occurrence is worth 1, the second 0.95, the third 0.92, etc.&lt;br&gt;
Here is the formula we’ll use:&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%2Fh0obt80iu7vvwr8mssgp.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%2Fh0obt80iu7vvwr8mssgp.png" alt="Image description" width="495" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, using a decay value of 0.97 with the query “black”.&lt;/p&gt;

&lt;p&gt;For a document with a frequency of the token “black” equal to 6, we get the following result:&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%2Ff5l6w3aa5utxy7xv2m63.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%2Ff5l6w3aa5utxy7xv2m63.png" alt="Image description" width="800" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The difference here is not striking (we would have had 6 before, now we have 5.4). But let’s calculate the frequency score with a token that appears even more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For 20:&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%2Funh9fo05f8nuc21jxvfz.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%2Funh9fo05f8nuc21jxvfz.png" alt="Image description" width="798" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For 50:&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%2Fx5vswkv1zv05xpdydh4x.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%2Fx5vswkv1zv05xpdydh4x.png" alt="Image description" width="788" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For 100:&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%2Fvsmufxulw8hy6de3yeo6.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%2Fvsmufxulw8hy6de3yeo6.png" alt="Image description" width="800" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We now have a way to differentiate between two documents that don’t have the same number of occurrences of a token, without overly favoring a document that contains this token a very large number of times.&lt;/p&gt;

&lt;p&gt;The impact of the term on the score decreases progressively.&lt;br&gt;
We therefore get a non-linear progression, and avoid arbitrarily stopping the point at which a token’s frequency no longer has an effect.&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;double&lt;/span&gt; &lt;span class="nf"&gt;GetScore&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;queryTokens&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;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;decay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.97&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;totalScore&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;int&lt;/span&gt; &lt;span class="n"&gt;matchToken&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="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;token&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;queryTokens&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;frequency&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&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="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&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;frequency&lt;/span&gt; &lt;span class="p"&gt;&amp;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;matchToken&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;

        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;freqScore&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="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frequency&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="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;decay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;totalScore&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;freqScore&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;matchToken&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;queryTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;matchToken&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;totalScore&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;matchToken&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;totalScore&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;But can we do better? Or rather: what other problems might still arise?&lt;/p&gt;

&lt;p&gt;Let’s try the following query: “the”, “animal”&lt;br&gt;
If we base ourselves only on the frequency score (and we can, since no document matches both tokens, so the boost from the number of matched tokens will have no effect here), the documents containing the token “the” will all have the same score:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;doc2 – 1&lt;/li&gt;
&lt;li&gt;doc3 – 1&lt;/li&gt;
&lt;li&gt;doc5 – 1&lt;/li&gt;
&lt;li&gt;doc6 – 1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the token “animal”, which appears only once in document 1, it will also receive a score of 1.&lt;/p&gt;

&lt;p&gt;We therefore once again end up with a lack of discrimination between the documents. One might think that, since each term appears only once in each document, we can’t do better… But that’s wrong!&lt;br&gt;
There is a difference: their frequency across the entire document set, not just within a single document.&lt;/p&gt;

&lt;p&gt;The word “the” appears in almost every document, whereas “animal” appears in only one.&lt;/p&gt;

&lt;p&gt;So there is a rarity difference — and we can take advantage of that.&lt;/p&gt;


&lt;h2&gt;
  
  
  TF-IDF
&lt;/h2&gt;

&lt;p&gt;Until now, we have only calculated what is called TF (Term Frequency), which is the frequency of a token in a document. It is now time to include in our score the Inverse Document Frequency (IDF), to measure the rarity of a word across all documents.&lt;/p&gt;

&lt;p&gt;The idea is that the rarer a word is, the more informative it is. Which is exactly the case here: the token “the”, present everywhere, doesn’t help refine the result.&lt;/p&gt;

&lt;p&gt;To calculate our IDF, we will use the following formula:&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%2F9jurwjgprwmw4qnmw1z8.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%2F9jurwjgprwmw4qnmw1z8.png" alt="Image description" width="214" height="76"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where &lt;em&gt;t&lt;/em&gt; is the token, &lt;em&gt;N&lt;/em&gt; the total number of documents in our corpus, and &lt;em&gt;nt&lt;/em&gt; the number of documents containing this token.&lt;/p&gt;

&lt;p&gt;Let’s calculate the IDF of “the”:&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%2Fosx92876vtt5b1cxzq51.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%2Fosx92876vtt5b1cxzq51.png" alt="Image description" width="354" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then of “animal”:&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%2Fapo71t9dvs885izzd98n.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%2Fapo71t9dvs885izzd98n.png" alt="Image description" width="334" height="92"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And let’s use this IDF in our score calculation, now called TF-IDF (the multiplication of TF by IDF):&lt;br&gt;
Since our TF is still equal to 1 here, the calculation is simplified: we only keep the IDF value.&lt;/p&gt;

&lt;p&gt;The document scores for our query are now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;doc1 – 0.778&lt;/li&gt;
&lt;li&gt;doc2 – 0.079&lt;/li&gt;
&lt;li&gt;doc3 – 0.079&lt;/li&gt;
&lt;li&gt;doc4 – 0.079&lt;/li&gt;
&lt;li&gt;doc5 – 0.079&lt;/li&gt;
&lt;li&gt;doc6 – 0.079&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if we had another document containing just the two tokens, it would rank first with a score of 0.857.&lt;/p&gt;

&lt;p&gt;Notice that we no longer even need to artificially boost the score based on the number of matched tokens in the query — a boost which was arbitrary. And that’s much better!&lt;/p&gt;

&lt;p&gt;We have the formula, now let’s move on to the code:&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;double&lt;/span&gt; &lt;span class="nf"&gt;GetScore&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;queryTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;decay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.97&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;totalScore&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="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;token&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;queryTokens&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;frequency&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&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="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&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;frequency&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="k"&gt;continue&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;df&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ReversedIndex&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;idf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&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="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;Documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&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="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;tf&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="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frequency&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="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;decay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;tfidf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tf&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;idf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;totalScore&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;tfidf&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;totalScore&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 frequency calculation doesn’t change: we simply retrieve the frequency of documents containing the token (df), thanks to our inverted index, which already contains this information.&lt;/p&gt;

&lt;p&gt;The total number of documents is also already known at indexing time.&lt;/p&gt;

&lt;p&gt;So we have our IDF, our TF, and therefore the final document score!&lt;/p&gt;

&lt;p&gt;But… can we do even better?&lt;br&gt;
Let’s look at the current limitations (even though we’re already doing pretty well).&lt;br&gt;
One of the problems comes from TF: as we’ve seen, it can increase very quickly and skew the score.&lt;/p&gt;

&lt;p&gt;At first, we implemented a saturation limit to prevent this. A limit that, normally, is not included in classic TF-IDF.&lt;/p&gt;

&lt;p&gt;But despite this, the algorithm actually favors long documents (via TF), even though a long document is not necessarily more relevant.&lt;br&gt;
Moreover, we have few parameters to tweak our results (except decay, which is also not present in the base version).&lt;/p&gt;

&lt;p&gt;It’s therefore time to move on to a new algorithm: BM25.&lt;/p&gt;


&lt;h2&gt;
  
  
  Best Match 25
&lt;/h2&gt;

&lt;p&gt;Best Match 25 is the 25ᵗʰ version of the algorithm meant to slightly address the problems of TF-IDF.&lt;/p&gt;

&lt;p&gt;First, here is its formula:&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%2F7iqekvpq5qfos8k32yif.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%2F7iqekvpq5qfos8k32yif.png" alt="Image description" width="600" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No need to run away—if you’ve made it this far, you have all the tools in hand to understand it, and we’ll go through it together.&lt;/p&gt;

&lt;p&gt;We already know the IDF, so no issue there. Let’s instead focus on what replaces the TF.&lt;/p&gt;

&lt;p&gt;Let’s start with the numerator:&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%2Fi1ep4dtvfdosbd1jjtf4.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%2Fi1ep4dtvfdosbd1jjtf4.png" alt="Image description" width="204" height="32"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;f(t,d) is the frequency of term t in document d.&lt;/li&gt;
&lt;li&gt;k1 +1 is a way to control saturation, exactly like our decay earlier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The smaller k1 is, the faster the saturation happens, which will reduce the impact of each additional token present in the document in a non-linear way.&lt;/p&gt;

&lt;p&gt;For the denominator, we again find our term frequency and the variable k1 for saturation, along with a normalization factor related to the document length.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;|d| is the actual length of the document (number of tokens it contains).&lt;/li&gt;
&lt;li&gt;avgdl is the average length of our indexed documents.&lt;/li&gt;
&lt;li&gt;b is a parameter that adjusts the importance of the normalization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, when a document is longer than average, its score will tend to decrease. On the other hand, a short document will see its score increase more quickly.&lt;/p&gt;

&lt;p&gt;Let’s look at this with an example. Let’s reuse our list of documents and exclude document 6, which was very long. We’ll search for the token “black”.&lt;/p&gt;

&lt;p&gt;Let’s start by defining our parameters k and b.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;k1 will be 1.5&lt;/li&gt;
&lt;li&gt;b will be 0.75&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(these are fairly common values for these parameters).&lt;/p&gt;

&lt;p&gt;Let’s also compute the average length of our documents, which is: 8 + 4 + 4 + 9 + 5, i.e., 30. Divided by the number of documents (5), we get an average length of 6.&lt;/p&gt;

&lt;p&gt;So avgdl equals 6.&lt;/p&gt;

&lt;p&gt;The token “black” appears in doc1 and doc3, with an IDF of 0.916.&lt;br&gt;
Let’s start by calculating the score for document 1. Its length |d| is 8.&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%2Fcbsalkpgw2u5azrr8k2h.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%2Fcbsalkpgw2u5azrr8k2h.png" alt="Image description" width="600" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s plug in the values in the numerator: term frequency in the document is 1, k1 is 1.5. We already know the IDF. So we get:&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%2Ful6joe9bsgp3drhcf0rs.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%2Ful6joe9bsgp3drhcf0rs.png" alt="Image description" width="532" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or:&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%2Fdeuixm1vej2sczsj3h6m.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%2Fdeuixm1vej2sczsj3h6m.png" alt="Image description" width="545" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the denominator, we can also replace known values (tf and k1):&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%2Fx2inxls183j17xi9lasd.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%2Fx2inxls183j17xi9lasd.png" alt="Image description" width="498" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We know the average document length and the current document length, so we can replace |d| and avgdl:&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%2Ff93fxo9es0fy3wdwpuzj.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%2Ff93fxo9es0fy3wdwpuzj.png" alt="Image description" width="473" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or:&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%2F5gbswzhc9xoscdkbeid3.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%2F5gbswzhc9xoscdkbeid3.png" alt="Image description" width="539" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;b is known, it is 0.75:&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%2Fikfg8j7azs0p6bbn3n3s.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%2Fikfg8j7azs0p6bbn3n3s.png" alt="Image description" width="560" height="92"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So we get:&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%2Fx4hxcpnchff7lk7zhxtu.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%2Fx4hxcpnchff7lk7zhxtu.png" alt="Image description" width="420" height="64"&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%2Fltd8qlucx7aw0q8vu61y.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%2Fltd8qlucx7aw0q8vu61y.png" alt="Image description" width="279" height="84"&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%2Fkkq5biltfkhh8p31bohw.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%2Fkkq5biltfkhh8p31bohw.png" alt="Image description" width="257" height="48"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A final score of &lt;strong&gt;0.796&lt;/strong&gt; for document 1.&lt;br&gt;
What about document 3?&lt;/p&gt;

&lt;p&gt;Document 3 is very similar: the term frequency is still 1, and the IDF is the same. The only part that changes is the document length, which instead of being 8 out of 6, is 4 out of 6.&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%2Ffkzgvuv12pn8pnkj0irf.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%2Ffkzgvuv12pn8pnkj0irf.png" alt="Image description" width="522" height="95"&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%2Fds038z4in99absrs45fa.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%2Fds038z4in99absrs45fa.png" alt="Image description" width="538" height="95"&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%2Fidxg6rwl8xwobnp7wzfw.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%2Fidxg6rwl8xwobnp7wzfw.png" alt="Image description" width="451" height="66"&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%2Fzy74w5393jhtu6r5bjhh.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%2Fzy74w5393jhtu6r5bjhh.png" alt="Image description" width="379" height="81"&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%2Fadaou87l5d3dut6vfpfu.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%2Fadaou87l5d3dut6vfpfu.png" alt="Image description" width="301" height="69"&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%2Femsyqq1q6efby011phuj.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%2Femsyqq1q6efby011phuj.png" alt="Image description" width="313" height="84"&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%2F2iwlsq1bsy31bs4nztw0.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%2F2iwlsq1bsy31bs4nztw0.png" alt="Image description" width="266" height="47"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A final score of &lt;strong&gt;1.077&lt;/strong&gt; for our document 3 — a better score than document 1!&lt;/p&gt;

&lt;p&gt;A lot of calculations were needed to demonstrate how BM25 works, but since the operations are quite simple, they’re actually very easy to translate into code:&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;double&lt;/span&gt; &lt;span class="nf"&gt;GetScore&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;queryTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;tfWeight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;docLengthPenalty&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;score&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;double&lt;/span&gt; &lt;span class="n"&gt;avgDocLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Average&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;doc&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;Length&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;token&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;queryTokens&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;tf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&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="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&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;tf&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="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ReversedIndex&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="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;continue&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;df&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ReversedIndex&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;idf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&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="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;Documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&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="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;norm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tfWeight&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tfWeight&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="n"&gt;docLengthPenalty&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;docLengthPenalty&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&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;span class="n"&gt;avgDocLength&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;idf&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;norm&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;score&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;Congratulations, you now know how to calculate a relevance score for a piece of content and return the best document to your user first.&lt;/p&gt;

&lt;p&gt;Of course, other parameters can be considered, like tokens found in the title, the document’s modification date, or even its location.&lt;/p&gt;

&lt;p&gt;Here, we focused on the content of a document, but this algorithm can be adapted and extended with other fields like the title, date, etc.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>csharp</category>
      <category>elasticsearch</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Comprendre et implémenter l'algo de score BM25</title>
      <dc:creator>Mary 🇪🇺 🇷🇴 🇫🇷</dc:creator>
      <pubDate>Tue, 17 Jun 2025 17:53:52 +0000</pubDate>
      <link>https://dev.to/pykpyky/comprendre-et-implementer-lalgo-de-score-bm25-47af</link>
      <guid>https://dev.to/pykpyky/comprendre-et-implementer-lalgo-de-score-bm25-47af</guid>
      <description>&lt;p&gt;En tant que SRE, je dois souvent chercher des informations dans des logs, des tas de logs, beaucoup d’informations, dans beaucoup de fichier. Et en règle générale, que ce soit sur internet ou même sur votre ordinateur, vous êtes confronté au même problème. Vous devez chercher de l’information dans des documents qui s’accumulent de plus en plus.&lt;/p&gt;

&lt;p&gt;On a là une problématique : quand je cherche le mot Panda sur mon ordinateur, quel fichier est-il censé me remonter en premier ? Le plus récent ? Non, le plus pertinent ! Et savoir si un document est pertinent par rapport à une recherche est le sujet du jour.&lt;/p&gt;

&lt;p&gt;Installez-vous, prenez un café, un thé ou un chocolat chaud. C’est parti !&lt;/p&gt;

&lt;p&gt;La problématique du jour peut être formalisée comme ceci : pour une recherche, je veux un score de pertinence lié à mes documents, et le premier document qui sortira de ma recherche sera celui avec le plus gros score !&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Avant de commencer, un petit mot : cet article contient plusieurs formules mathématiques. Pas de panique, on va traverser ça ensemble étape par étape, et je vais tout expliquer clairement pour que ce soit simple à comprendre.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On va commencer avec une petite liste de documents pour nous accompagner tout au long.&lt;br&gt;
C’est parti pour les présentations :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Doc 1 : « Un panda est un animal blanc et noir »&lt;/li&gt;
&lt;li&gt;Doc 2 : « Le chien est blanc »&lt;/li&gt;
&lt;li&gt;Doc 3 : « Le chat est noir »&lt;/li&gt;
&lt;li&gt;Doc 4 : « Le panda n'est ni un chat ni un chien »&lt;/li&gt;
&lt;li&gt;Doc 5 : « Le panda roux est roux »&lt;/li&gt;
&lt;li&gt;Doc 6 :« Noir c'est noir, il n'y a vraiment plus d'espoir
Je suis dans le noir, j'ai du mal à croire
Noir c'est noir, il n'est jamais trop tard
Noir c'est noir, il me reste l'espoir
Noir c'est noir, il me reste l'espoir
Noir c'est noir, il me reste l'espoir »&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;C’est un jeu de test assez simple, mais qui permet déjà de voir un peu le problème du scoring.&lt;/p&gt;

&lt;p&gt;Commençons de façon intuitive : si je cherche le mot « noir », quel document est le plus pertinent ? Entre les documents 1, 3 et 6 ?&lt;/p&gt;

&lt;p&gt;Pas facile hein ?&lt;/p&gt;

&lt;p&gt;Avant d’entrer un peu plus dans la problématique, posons quelques définitions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token&lt;/strong&gt;&lt;br&gt;
Nos documents, et même notre recherche, ne sont pas composés de "mots", mais de ce qu’on va appeler des tokens.&lt;br&gt;
Pour ce blog post, un mot = un token. Donc pas de gros changement, mais comme on va utiliser le mot token tout au long de l’article, autant le dire dès maintenant.&lt;br&gt;
Par exemple, le doc 1 contient les tokens suivants :&lt;br&gt;
[« Un », « panda », « est », « un », « animal », « blanc », « et », « noir »]&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Index inversé&lt;/strong&gt;&lt;br&gt;
Quand on cherche le mot noir dans nos documents, on ne va pas les ouvrir un par un pour voir si le mot est dedans.&lt;br&gt;
Avant toute recherche, notre application doit indexer les documents : les parcourir pour extraire les tokens présents dans chacun. Pendant cette étape, on construit ce qu’on appelle un index inversé.&lt;br&gt;
Un index inversé, est un dictionnaire où :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;La clé, est un token&lt;/li&gt;
&lt;li&gt;La valeur associée, est la liste des documents qui contiennent ce token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Par exemple, notre index inversé contient les tokens/clés :&lt;br&gt;
« le », « panda », « est », « un », « animal », « noir », « et », « blanc », « chien », « chat », …&lt;br&gt;
La clé « panda », est associée aux documents 1, 4 et 5.&lt;br&gt;
La clé « noir », est associée aux documents 1, 3 et 6.&lt;br&gt;
Et ainsi de suite.&lt;/p&gt;

&lt;p&gt;Voyons aussi un léger aperçu des classes que nous allons manipuler.&lt;/p&gt;

&lt;p&gt;Nous avons en premier la classe &lt;code&gt;Document&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Document&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="n"&gt;Id&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="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="n"&gt;Path&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="n"&gt;Content&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;int&lt;/span&gt; &lt;span class="n"&gt;Length&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ContentTokens&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TitleTokens&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;DateTime&lt;/span&gt; &lt;span class="n"&gt;IndexedAt&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="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&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;Et le résultat de notre recherche, via la classe &lt;code&gt;SearchResult&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SearchResult&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="n"&gt;Id&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="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="n"&gt;Path&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="n"&gt;ContentSnippet&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;double&lt;/span&gt; &lt;span class="n"&gt;Score&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;DateTime&lt;/span&gt; &lt;span class="n"&gt;IndexedAt&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;Notre résultat final est donc une liste d’objets &lt;code&gt;SearchResult&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Algorithme de score, simple, basique
&lt;/h2&gt;

&lt;p&gt;Une première façon de faire que l’on pourrait imaginer, c’est de compter le nombre de fois qu’un token apparaît dans le document. Si je cherche le token « noir », il apparaît une fois dans le doc1, une fois dans le doc3 et 11 fois dans le doc6.&lt;br&gt;
Donnons donc un score de 1 à chaque fois que le token est présent. Cela se fait très facilement en C# avec 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="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;GetScore&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;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&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;score&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;var&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&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="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&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;score&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;Ici, nous comptons le nombre de fois où un token de notre requête est présent dans le document.&lt;/p&gt;

&lt;p&gt;On aura donc un score de 1 pour les documents 1 et 3, et un score de 11 pour le document 6.&lt;/p&gt;

&lt;p&gt;Bon… mis à part que le nombre de tokens n’est pas forcément un bon indicateur, on va vite se rendre compte d'un autre problème. Si ma requête est « chat », « noir », le document 6 aura toujours un score de 11, alors que le document 3, qui contient les deux tokens, n’aura qu’un score de 2.&lt;/p&gt;

&lt;p&gt;Alors qu’il contient les deux tokens de ma requête ! Il est donc logiquement, pour nous, plus pertinent !&lt;/p&gt;

&lt;p&gt;Pour régler ce premier problème, on peut ajouter un boost au score final quand un document contient tous les tokens.&lt;br&gt;
Mais surtout, on peut tenter de réduire l’impact d’un token qui apparaît plusieurs fois dans un document.&lt;/p&gt;

&lt;p&gt;Est-ce qu’un document qui contient 50 fois le token « noir » doit avoir un score deux fois supérieur à un document qui contient 25 fois le même token ?&lt;br&gt;
Probablement pas. Être devant, oui, mais pas autant !&lt;/p&gt;

&lt;p&gt;Et puis, les documents 1 et 3 ont le même score (1) pour le token « noir ».&lt;br&gt;
Nous n'avons aucun moyen de les départager avec cette simple méthode.&lt;/p&gt;


&lt;h2&gt;
  
  
  Une version améliorée de la fonction de score
&lt;/h2&gt;

&lt;p&gt;On peut modifier notre fonction de score pour :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calculer le score pour tous les tokens de la requête&lt;/li&gt;
&lt;li&gt;Limiter l’impact qu’un seul token peut avoir sur le score final&lt;/li&gt;
&lt;li&gt;Booster les résultats qui matchent plusieurs tokens
&lt;/li&gt;
&lt;/ul&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;int&lt;/span&gt; &lt;span class="nf"&gt;GetScore&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;queryTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&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;totalScore&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;int&lt;/span&gt; &lt;span class="n"&gt;matchToken&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="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;token&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;queryTokens&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;score&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&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="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&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;score&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;score&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;&amp;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;matchToken&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;

        &lt;span class="n"&gt;totalScore&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;totalScore&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;matchToken&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;totalScore&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;Désormais, un token ne peut plus influencer le score au-delà de 5.&lt;br&gt;
Et le score du document gagne 10 points par token trouvé dans la requête.&lt;/p&gt;

&lt;p&gt;Faisons un nouvel essai avec la requête « chat », « noir » :&lt;br&gt;
Les documents correspondants sont : 1, 3, 4 et 6.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Les documents 1 et 4, qui ne contiennent qu’un seul token chacun, auront un score de 11 : 1 point pour la fréquence du token, et 10 points car ils matchent un token présent dans la requête.&lt;/li&gt;
&lt;li&gt;Le document 3 aura un score de 22 : 2 points pour la présence de « chat » et « noir », et 20 points pour avoir matché 2 tokens de la requête.&lt;/li&gt;
&lt;li&gt;Le document 6 aura 15 points : 5 points pour la fréquence du token « noir » (limitée à 5), et 10 points pour avoir matché 1 token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Résultat final :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Doc 3 → 22 points&lt;/li&gt;
&lt;li&gt;Doc 6 → 15 points&lt;/li&gt;
&lt;li&gt;Doc 1 et Doc 4 → 11 points&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a réglé nos deux premiers problèmes… mais on peut faire mieux !&lt;/p&gt;

&lt;p&gt;Ici, le vrai problème, c’est la limite à 5.&lt;br&gt;
Ce n’est pas tant la valeur "5" qui pose problème, mais le fait qu’on a une augmentation linéaire du score qui s’arrête brutalement à une limite arbitraire.&lt;/p&gt;


&lt;h2&gt;
  
  
  Les rendements décroissants
&lt;/h2&gt;

&lt;p&gt;Pour régler ce problème, on ne va pas instaurer une limite fixe au-delà de laquelle la fréquence d’un terme ne rapporte plus de points, mais plutôt un rendement décroissant. Par exemple, la première occurrence vaut 1, la deuxième 0.95, la troisième 0.92, etc.&lt;br&gt;
Voici la formule que l’on va utiliser :&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%2Fh0obt80iu7vvwr8mssgp.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%2Fh0obt80iu7vvwr8mssgp.png" alt="Image description" width="495" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Par exemple, utilisons une valeur de decay à 0.97, pour la requête « noir ».&lt;/p&gt;

&lt;p&gt;Pour un document qui aurai une fréquence du token « noir » de 6, obtient le résultat suivant :&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%2Ff5l6w3aa5utxy7xv2m63.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%2Ff5l6w3aa5utxy7xv2m63.png" alt="Image description" width="800" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;La différence n’est ici pas flagrante (on aurai eu 6 avant, on a 5.4 maintenant). Mais calculons le score de fréquence avec un token qui apparaîtrait encore plus :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pour 20 :&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%2Funh9fo05f8nuc21jxvfz.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%2Funh9fo05f8nuc21jxvfz.png" alt="Image description" width="798" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pour 50 :&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%2Fx5vswkv1zv05xpdydh4x.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%2Fx5vswkv1zv05xpdydh4x.png" alt="Image description" width="788" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pour 100 :&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%2Fvsmufxulw8hy6de3yeo6.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%2Fvsmufxulw8hy6de3yeo6.png" alt="Image description" width="800" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On a donc bien un moyen de différencier deux documents qui n’auraient pas le même nombre d’occurrences d’un token, sans pour autant favoriser exagérément un document qui contient ce token un très grand nombre de fois.&lt;/p&gt;

&lt;p&gt;L’impact du terme sur le score diminue progressivement.&lt;br&gt;
On obtient donc une évolution non linéaire, et on évite d’arrêter arbitrairement le moment où la fréquence d’un token cesse d’avoir un effet.&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;double&lt;/span&gt; &lt;span class="nf"&gt;GetScore&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;queryTokens&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;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;decay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.97&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;totalScore&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;int&lt;/span&gt; &lt;span class="n"&gt;matchToken&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="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;token&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;queryTokens&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;frequency&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&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="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&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;frequency&lt;/span&gt; &lt;span class="p"&gt;&amp;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;matchToken&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;

        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;freqScore&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="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frequency&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="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;decay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;totalScore&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;freqScore&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;matchToken&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;queryTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;matchToken&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;totalScore&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;matchToken&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;totalScore&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;Mais est-ce qu’on peut faire mieux ? Ou plutôt : quels autres problèmes pouvons-nous encore avoir ?&lt;/p&gt;

&lt;p&gt;Faisons la requête suivante : « le », « animal »&lt;br&gt;
Si on se base uniquement sur le score de fréquence (et on peut le faire, car aucun document ne matche les deux tokens, donc le boost donné par le nombre de tokens matchés n’aura ici aucun effet), les documents contenant le token « le » auront tous le même score :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;doc2 – 1&lt;/li&gt;
&lt;li&gt;doc3 – 1&lt;/li&gt;
&lt;li&gt;doc5 – 1&lt;/li&gt;
&lt;li&gt;doc6 - 1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour le token « animal », qui n’est présent qu’une seule fois dans le document 1, il obtiendra également un score de 1.&lt;/p&gt;

&lt;p&gt;On se retrouve donc encore une fois avec un manque de discrimination entre les documents. On pourrait penser que, comme chaque terme n’apparaît qu’une fois dans chaque document, on ne peut pas mieux faire... Mais c’est faux !&lt;br&gt;
Il existe une différence : leur fréquence dans l’ensemble des documents, pas juste dans un document.&lt;/p&gt;

&lt;p&gt;Le mot « le » apparaît dans presque tous les documents, alors que « animal» n’apparaît que dans un seul.&lt;/p&gt;

&lt;p&gt;Il y a donc une différence de rareté — et cette rareté, on peut l’exploiter.&lt;/p&gt;


&lt;h2&gt;
  
  
  TF-IDF
&lt;/h2&gt;

&lt;p&gt;Jusqu’à présent, nous avons uniquement calculé ce qu’on appelle le TF (Term Frequency), c’est-à-dire la fréquence d’un token dans un document. Il est maintenant temps d’inclure dans notre score le Inverse Document Frequency (IDF), pour mesurer la rareté d’un mot dans l’ensemble des documents.&lt;/p&gt;

&lt;p&gt;L’idée est de se dire que plus un mot est rare, plus il est informatif. Ce qui est exactement le cas ici : le token « le», présent partout, n’aide pas à affiner le résultat.&lt;/p&gt;

&lt;p&gt;Pour calculer notre IDF, nous allons utiliser la formule suivante :&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%2F9jurwjgprwmw4qnmw1z8.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%2F9jurwjgprwmw4qnmw1z8.png" alt="Image description" width="214" height="76"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Où t est le token, N le nombre total de documents dans notre corpus, et nt le nombre de documents qui contiennent ce token.&lt;/p&gt;

&lt;p&gt;Calculons l’IDF de « le » :&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%2Fosx92876vtt5b1cxzq51.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%2Fosx92876vtt5b1cxzq51.png" alt="Image description" width="354" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Puis de « animal » :&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%2Fapo71t9dvs885izzd98n.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%2Fapo71t9dvs885izzd98n.png" alt="Image description" width="334" height="92"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Et utilisons cette IDF dans notre calcul de score, qui s’appelle désormais TF-IDF (la multiplication de TF par IDF) :&lt;br&gt;
Vu que notre TF est toujours égal à 1 ici, le calcul est simplifié : on garde uniquement la valeur de l’IDF.&lt;br&gt;
Les scores des documents pour notre requête sont donc maintenant :&lt;br&gt;
doc1 – 0.778 &lt;br&gt;
doc2 – 0.079&lt;br&gt;
doc3 – 0.079&lt;br&gt;
doc4 – 0.079&lt;br&gt;
doc5 – 0.079&lt;br&gt;
doc6 – 0.079&lt;/p&gt;

&lt;p&gt;Et si on avait un autre document ne contenant que les 2 tokens il se placerait en 1er position avec le score de 0.857 &lt;/p&gt;

&lt;p&gt;Remarquez qu’on n’a même plus besoin de booster artificiellement le score en fonction du nombre de tokens matchés dans la requête — un boost qui était arbitraire. Et c’est beaucoup mieux !&lt;/p&gt;

&lt;p&gt;On a la formule, passons maintenant au code :&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;double&lt;/span&gt; &lt;span class="nf"&gt;GetScore&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;queryTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;decay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.97&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;totalScore&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="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;token&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;queryTokens&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;frequency&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&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="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&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;frequency&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="k"&gt;continue&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;df&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ReversedIndex&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;idf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&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="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;Documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&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="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;tf&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="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;decay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frequency&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="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;decay&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;tfidf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tf&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;idf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;totalScore&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;tfidf&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;totalScore&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;Le calcul de fréquence ne change pas : on récupère simplement la fréquence des documents contenant le token (df), grâce à notre index inversé, qui contient déjà cette information.&lt;/p&gt;

&lt;p&gt;Le nombre total de documents est lui aussi déjà connu au moment de l’indexation.&lt;/p&gt;

&lt;p&gt;On a donc notre IDF, notre TF, et donc le score final du document !&lt;/p&gt;

&lt;p&gt;Mais… est-ce qu’on peut faire encore mieux ?&lt;br&gt;
Voyons les limites actuelles (même si on est déjà pas mal).&lt;br&gt;
Un des problèmes vient du TF : comme on l’a vu, il peut augmenter très vite et biaiser le score.&lt;/p&gt;

&lt;p&gt;Dans un premier temps, on avait mis en place une limite de saturation pour éviter ça. Limite qui, normalement, n’est pas prévue dans le TF-IDF classique.&lt;/p&gt;

&lt;p&gt;Mais malgré ça, cet algorithme favorise en réalité les documents longs (via le TF), alors qu’un document long n’est pas forcément plus pertinent.&lt;br&gt;
De plus, on a peu de paramètres sur lesquels jouer pour fine-tuner un peu nos résultats (à part le decay, qui n’est pas non plus présent dans la version de base).&lt;/p&gt;

&lt;p&gt;Il est donc temps de passer à un nouvel algorithme : BM25.&lt;/p&gt;
&lt;h2&gt;
  
  
  Best Match 25
&lt;/h2&gt;

&lt;p&gt;Best Match 25 est la 25ᵉ version de l'algorithme censé combler un peu les problèmes du TF-IDF.&lt;/p&gt;

&lt;p&gt;Voici d’abord sa formule :&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%2F7iqekvpq5qfos8k32yif.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%2F7iqekvpq5qfos8k32yif.png" alt="Image description" width="600" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inutile de partir en courant, si vous êtes arrivé jusqu’ici, vous avez toutes les cartes en main pour la comprendre, et on va voir ça ensemble.&lt;/p&gt;

&lt;p&gt;L’IDF, on le connaît déjà, aucun problème donc. On va plutôt regarder ce qui vient remplacer le TF.&lt;/p&gt;

&lt;p&gt;Commençons par le numérateur :&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%2Fi1ep4dtvfdosbd1jjtf4.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%2Fi1ep4dtvfdosbd1jjtf4.png" alt="Image description" width="204" height="32"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;f(t,d) est la fréquence du terme t dans le document d.&lt;/li&gt;
&lt;li&gt;k1 +1 est un moyen de contrôler la saturation, exactement comme notre decay plus haut. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus k1 est petit, plus la saturation est rapide, ce qui réduira de façon non linéaire l’impact de chaque nouveau token présent dans le document.&lt;/p&gt;

&lt;p&gt;Pour le dénominateur, on retrouve encore notre fréquence et notre variable k1 pour la saturation, ainsi qu’un facteur de normalisation lié à la longueur du document.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;|d| est la longueur actuelle du document (le nombre de tokens qu’il contient).&lt;/li&gt;
&lt;li&gt;avgdl est la longueur moyenne de nos documents indexés&lt;/li&gt;
&lt;li&gt;b est un paramètre qui sert à ajuster l’importance de la normalisation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quand nous avons un document qui est donc plus long que la moyenne, le score aura donc tendance à diminuer. Au contraire, un document court verra son score progresser plus rapidement.&lt;/p&gt;

&lt;p&gt;Voyons ça avec un exemple. Reprenons notre liste de documents, et excluons le document 6 qui était très long. Et cherchons le token « noir ».&lt;/p&gt;

&lt;p&gt;Commençons par définir nos paramètres k1 et b. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;k1 sera 1.5&lt;/li&gt;
&lt;li&gt;b sera 0.75 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(ce sont des valeurs assez communes pour ces paramètres).&lt;/p&gt;

&lt;p&gt;Calculons aussi la longueur moyenne de nos documents qui est : 8 + 4 + 4 + 9 + 5, soit 30. Divisé par le nombre de documents (5), ça nous donne une longueur moyenne de 6. &lt;/p&gt;

&lt;p&gt;avgdl est donc égal à 6.&lt;/p&gt;

&lt;p&gt;Le token « noir » est présent dans doc1 et doc3, avec un IDF de 0.916.&lt;br&gt;
Commençons par calculer le score du document 1. Sa longueur |d| est de 8.&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%2Fcbsalkpgw2u5azrr8k2h.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%2Fcbsalkpgw2u5azrr8k2h.png" alt="Image description" width="600" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Commençons par remplacer les valeurs du numérateur : la fréquence du terme dans le document est de 1, la valeur de k1 est de 1.5. On connaît aussi déjà l’IDF. On a donc :&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%2Ful6joe9bsgp3drhcf0rs.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%2Ful6joe9bsgp3drhcf0rs.png" alt="Image description" width="532" height="106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ou :&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%2Fdeuixm1vej2sczsj3h6m.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%2Fdeuixm1vej2sczsj3h6m.png" alt="Image description" width="545" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Au dénominateur, on peut aussi facilement remplacer les valeurs déjà connues (tf et k1) :&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%2Fx2inxls183j17xi9lasd.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%2Fx2inxls183j17xi9lasd.png" alt="Image description" width="498" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On connaît la longueur moyenne des documents et la longueur du document actuel, on peut donc remplacer |d| et avgdl :&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%2Ff93fxo9es0fy3wdwpuzj.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%2Ff93fxo9es0fy3wdwpuzj.png" alt="Image description" width="473" height="89"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ou :&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%2F5gbswzhc9xoscdkbeid3.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%2F5gbswzhc9xoscdkbeid3.png" alt="Image description" width="539" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;b est connu, il est de 0.75 :&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%2Fikfg8j7azs0p6bbn3n3s.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%2Fikfg8j7azs0p6bbn3n3s.png" alt="Image description" width="560" height="92"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On a donc :&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%2Fx4hxcpnchff7lk7zhxtu.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%2Fx4hxcpnchff7lk7zhxtu.png" alt="Image description" width="420" height="64"&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%2Fltd8qlucx7aw0q8vu61y.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%2Fltd8qlucx7aw0q8vu61y.png" alt="Image description" width="279" height="84"&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%2Fkkq5biltfkhh8p31bohw.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%2Fkkq5biltfkhh8p31bohw.png" alt="Image description" width="257" height="48"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Un score final donc de 0.796 pour le document 1. Qu’en est-il du document 3 ?&lt;/p&gt;

&lt;p&gt;Le document 3 est très similaire : la fréquence du terme est toujours de 1 et l’IDF est le même. La seule partie qui change est bien sûr la longueur du document, qui au lieu d’être de 8 sur 6, est de 4 sur 6.&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%2Ffkzgvuv12pn8pnkj0irf.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%2Ffkzgvuv12pn8pnkj0irf.png" alt="Image description" width="522" height="95"&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%2Fds038z4in99absrs45fa.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%2Fds038z4in99absrs45fa.png" alt="Image description" width="538" height="95"&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%2Fidxg6rwl8xwobnp7wzfw.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%2Fidxg6rwl8xwobnp7wzfw.png" alt="Image description" width="451" height="66"&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%2Fzy74w5393jhtu6r5bjhh.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%2Fzy74w5393jhtu6r5bjhh.png" alt="Image description" width="379" height="81"&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%2Fadaou87l5d3dut6vfpfu.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%2Fadaou87l5d3dut6vfpfu.png" alt="Image description" width="301" height="69"&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%2Femsyqq1q6efby011phuj.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%2Femsyqq1q6efby011phuj.png" alt="Image description" width="313" height="84"&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%2F2iwlsq1bsy31bs4nztw0.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%2F2iwlsq1bsy31bs4nztw0.png" alt="Image description" width="266" height="47"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Un score final de 1.077 pour notre document 3. Un meilleur score que notre document 1 !&lt;/p&gt;

&lt;p&gt;Beaucoup de calculs ont été nécessaires pour démontrer comment marche BM25, mais les opérations étant assez simples, elles sont en fait très faciles à traduire en code :&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;double&lt;/span&gt; &lt;span class="nf"&gt;GetScore&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;queryTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;Document&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;tfWeight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;docLengthPenalty&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;score&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;double&lt;/span&gt; &lt;span class="n"&gt;avgDocLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Average&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;doc&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;Length&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;token&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;queryTokens&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;tf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&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="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&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;tf&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="p"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ReversedIndex&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="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;continue&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;df&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ReversedIndex&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;idf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&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="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;Documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&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="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;norm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tfWeight&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tfWeight&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="n"&gt;docLengthPenalty&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;docLengthPenalty&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&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;span class="n"&gt;avgDocLength&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;idf&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;norm&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;score&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;Bravo, vous savez maintenant comment calculer un score de pertinence sur un contenu et renvoyer en premier le meilleur document à votre utilisateur.&lt;br&gt;
Bien sûr, d’autres paramètres peuvent être pris en compte, comme les tokens trouvés dans le titre, la date de modification du document ou même sa localisation.&lt;/p&gt;

&lt;p&gt;Ici, nous nous sommes concentrés sur le contenu d'un document, mais cet algorithme peut être adapté et complété avec la d’autres champs comme le titre, la date, etc.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>algorithms</category>
      <category>csharp</category>
      <category>elasticsearch</category>
    </item>
    <item>
      <title>SDK Playground: Who Are You Blocking on BlueSky?</title>
      <dc:creator>Mary 🇪🇺 🇷🇴 🇫🇷</dc:creator>
      <pubDate>Wed, 07 May 2025 08:16:46 +0000</pubDate>
      <link>https://dev.to/pykpyky/sdk-playground-who-are-you-blocking-on-bluesky-3o6m</link>
      <guid>https://dev.to/pykpyky/sdk-playground-who-are-you-blocking-on-bluesky-3o6m</guid>
      <description>&lt;p&gt;Most services — including social networks — offer APIs that allow third-party programs or tech-savvy users to perform actions that would normally be manual and tedious, all through code.&lt;/p&gt;

&lt;p&gt;Let’s imagine I’m blocking people on BlueSky, and I want to know if another account has blocked the same people as I have. I would need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve my own block list&lt;/li&gt;
&lt;li&gt;Retrieve the block list of the other account&lt;/li&gt;
&lt;li&gt;And for each name, check if it's present in both lists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not impossible to do — at least on BlueSky, since everything is public. But it’s a massive pain.&lt;/p&gt;

&lt;p&gt;Another example: imagine I want to block everyone who liked a post (say, because it’s a post praising Kubernetes and I don’t like Kubernetes).&lt;/p&gt;

&lt;p&gt;Here’s what I’d have to do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find the post&lt;/li&gt;
&lt;li&gt;Retrieve the list of people who liked it&lt;/li&gt;
&lt;li&gt;Block them one by one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s not lie to ourselves — that’s boring as hell to do!&lt;/p&gt;




&lt;p&gt;Especially since not all services are created equal when it comes to APIs. Between Twitter’s ridiculous pricing (yes, I still call it Twitter — what are you gonna do, Elon?), LinkedIn’s extreme restrictions, or Yammer’s complete lack of documentation, we could write an entire article just about different API designs.&lt;/p&gt;

&lt;p&gt;There are still a few good students when it comes to APIs, and BlueSky is one of them. Mainly because their web app is built on top of their own API — which definitely helps keep things clean.&lt;/p&gt;

&lt;p&gt;But no matter how clean BlueSky’s API is, it’s still an API — a REST API, which means a web protocol. REST APIs are great: they can be used with pretty much any language (even Bash, which tells you a lot!)&lt;/p&gt;

&lt;p&gt;Still, between handling async calls and HTTP response codes that are longer than your holiday shopping list, it can be... annoying to manage cleanly.&lt;/p&gt;

&lt;p&gt;That’s where SDKs come in. SDKs are libraries for your language that make it easier to work with a service.&lt;/p&gt;

&lt;p&gt;For BlueSky, you’ve got TypeScript and Python SDKs, for example (and honestly, I get the point of a Python SDK — but TypeScript? Web devs are already weird enough, they’re used to hitting REST APIs manually anyway).&lt;/p&gt;

&lt;p&gt;But if you're coding in something else, like Java or C#, you’ll have no choice but to manage those REST calls yourself.&lt;/p&gt;

&lt;p&gt;Fortunately, my fellow C# developers, no worries — we’re going to look at how to solve these problems in C#!&lt;/p&gt;




&lt;h2&gt;
  
  
  Hello World
&lt;/h2&gt;

&lt;p&gt;First things first: we need a SDK!&lt;br&gt;
Good news — there’s one available on NuGet called &lt;code&gt;ATbluePandaSDK&lt;/code&gt;, which lets you easily interact with BlueSky in C#.&lt;/p&gt;

&lt;p&gt;To install it, just use your NuGet package manager or run this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package ATbluePandaSDK &lt;span class="nt"&gt;--version&lt;/span&gt; 0.1.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before diving into a complex use case, let’s start with something simple and classic: a little Hello World.&lt;/p&gt;

&lt;p&gt;Open up your project called &lt;code&gt;ConsoleApp1&lt;/code&gt; — because we’re not here to waste time naming things.&lt;/p&gt;

&lt;p&gt;Here’s a small snippet to get started:&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;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK.Models.Auth&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="n"&gt;ATPClient&lt;/span&gt; &lt;span class="n"&gt;client&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;ATPClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;AuthRequest&lt;/span&gt; &lt;span class="n"&gt;authRequest&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;AuthRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle.bsky.social"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BskyAuthUser&lt;/span&gt; &lt;span class="n"&gt;authUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreatePost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello world from ATbluePandaSDK version 0.1.2"&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 run this code, you’ll see “Hello, World!” in your console.&lt;br&gt;
But the real magic happens on your BlueSky account — you should now see 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%2Ft69ixozyc83lnpvux4s7.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%2Ft69ixozyc83lnpvux4s7.png" alt="BlueSky Post saying hello world" width="624" height="229"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;The code is short, but let’s break it down:&lt;/p&gt;

&lt;p&gt;We start by creating a &lt;code&gt;ATPClient&lt;/code&gt; object. This is &lt;em&gt;the&lt;/em&gt; core class of the SDK — everything you want to do on BlueSky goes through this client.&lt;/p&gt;

&lt;p&gt;But to actually &lt;em&gt;do&lt;/em&gt; things, we need to log in. For that, we create an &lt;code&gt;AuthRequest&lt;/code&gt; object with your handle and password.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“But to do things, we need to log in”&lt;/em&gt; — Well, not entirely true. Some endpoints don’t require authentication, but posting — and most things we’ll want to do — definitely does.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We then call the &lt;code&gt;Authenticate&lt;/code&gt; method on the &lt;code&gt;ATPClient&lt;/code&gt;, passing in our &lt;code&gt;AuthRequest&lt;/code&gt;. This returns a &lt;code&gt;BskyAuthUser&lt;/code&gt; object — that’s &lt;em&gt;you&lt;/em&gt;, your identity on BlueSky. It contains key info like your DID, handle, and most importantly, your &lt;strong&gt;token&lt;/strong&gt; — which you'll use to make further API calls without having to re-enter your password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never share this token with anyone!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finally, we use &lt;code&gt;CreatePost&lt;/code&gt; to publish our message. It just takes the text you want to post.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice we didn’t explicitly pass in the &lt;code&gt;BskyAuthUser&lt;/code&gt;. We could have, but the &lt;code&gt;ATPClient&lt;/code&gt; keeps it in memory after authentication — so you can skip that in future calls.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;Congrats! You just posted your first Hello World using the BlueSky C# SDK.&lt;br&gt;
Feeling emotional? Hold on — this is just the beginning!&lt;/p&gt;


&lt;h2&gt;
  
  
  A Real Use Case
&lt;/h2&gt;

&lt;p&gt;Alright, you’ve tested the first features of the SDK to make sure everything is working fine. Posting on BlueSky via your IDE is very cool, but it’s probably less user-friendly than doing it through the web app. So let’s go a bit further with our very first example: &lt;strong&gt;finding out which accounts are blocked by both you and another user&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What’s the point of this? I don’t know—maybe a future Tinder-like app will match people based on the percentage of users they’ve both blocked!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Let’s start by retrieving your own list of blocked users and printing their names:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATbluePandaSDK.Models.Account&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK.Models.Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ATPClient&lt;/span&gt; &lt;span class="n"&gt;client&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;ATPClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;AuthRequest&lt;/span&gt; &lt;span class="n"&gt;authRequest&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;AuthRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle.bsky.social"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BskyAuthUser&lt;/span&gt; &lt;span class="n"&gt;authUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&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;Block&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listOfUsersBlocked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBlocks&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="n"&gt;Block&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;listOfUsersBlocked&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="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;displayName&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 code gives me the following output:&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%2F05w4yzxnj54zcg4sl8pm.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%2F05w4yzxnj54zcg4sl8pm.png" alt="People I blocked on bsky" width="624" height="813"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, you’ll see your own blocked users, but don’t be shy about sharing your list—it’s public anyway!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: these are the people &lt;strong&gt;you personally blocked&lt;/strong&gt;—those blocked through moderation lists won’t appear here!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s walk through the code from the login step!&lt;/p&gt;

&lt;p&gt;With our client, we call the &lt;code&gt;GetBlocks&lt;/code&gt; function, which returns a list of blocked users (tough luck for them!) — this function runs for the &lt;strong&gt;currently authenticated user&lt;/strong&gt;, so just &lt;strong&gt;you&lt;/strong&gt;, and only you!&lt;/p&gt;

&lt;p&gt;We loop through the list, and among the attributes of the &lt;code&gt;Block&lt;/code&gt; object, we have the &lt;code&gt;displayName&lt;/code&gt; (there’s more info, but it’s not useful for now).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is &lt;code&gt;Block&lt;/code&gt; a &lt;code&gt;User&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
Yes, there's also a &lt;code&gt;User&lt;/code&gt; object that contains more information, but &lt;code&gt;Block&lt;/code&gt; has most of the user-related data, so you can easily convert a &lt;code&gt;Block&lt;/code&gt; to a &lt;code&gt;User&lt;/code&gt; if needed!&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Next step: get the list of people blocked by someone else!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this, we’ll need two more client functions:&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;using&lt;/span&gt; &lt;span class="nn"&gt;ATbluePandaSDK.Models.Account&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK.Models.Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ATPClient&lt;/span&gt; &lt;span class="n"&gt;client&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;ATPClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;AuthRequest&lt;/span&gt; &lt;span class="n"&gt;authRequest&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;AuthRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle.bsky.social"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BskyAuthUser&lt;/span&gt; &lt;span class="n"&gt;authUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&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;Block&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listOfUsersBlocked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBlocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUserProfil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"otherhandle.bsky.social"&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;Records&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;otherListOfUsersBlocked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAccountsBlocked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&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 time, we need an actor (which is a &lt;code&gt;User&lt;/code&gt; object).&lt;br&gt;
There are many ways to get one—here we used the &lt;code&gt;GetUserProfil&lt;/code&gt; function, which retrieves full user info. You can pass either a DID or a handle as a parameter.&lt;/p&gt;

&lt;p&gt;So now we have &lt;strong&gt;two lists&lt;/strong&gt;: one of &lt;code&gt;Block&lt;/code&gt; (the users you blocked) and one of &lt;code&gt;Record&lt;/code&gt; (a bit of a catch-all term in BlueSky — but in our case, also blocked users!).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We won’t go into detail here about what a &lt;code&gt;Record&lt;/code&gt; is—this post is already too long!&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;We now have two lists that we can easily cross-reference—thankfully, we’re using C# and not Bash!&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;using&lt;/span&gt; &lt;span class="nn"&gt;ATbluePandaSDK.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATbluePandaSDK.Models.Account&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK.Models.Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ATPClient&lt;/span&gt; &lt;span class="n"&gt;client&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;ATPClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;AuthRequest&lt;/span&gt; &lt;span class="n"&gt;authRequest&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;AuthRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle.bsky.social"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BskyAuthUser&lt;/span&gt; &lt;span class="n"&gt;authUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&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;Block&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listOfUsersBlocked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBlocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUserProfil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"otherhandle.bsky.social"&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;Records&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;otherListOfUsersBlocked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAccountsBlocked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&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="n"&gt;Block&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;listOfUsersBlocked&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="n"&gt;Records&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;otherListOfUsersBlocked&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;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;did&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&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="n"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;userBlocked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUserProfil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;did&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="n"&gt;userBlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DisplayName&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="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&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="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Here’s my output:&lt;/strong&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%2F9qchb56fiiarotq50a8f.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%2F9qchb56fiiarotq50a8f.png" alt="List of people blocked by myself AND another user" width="599" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, the list is shorter than before (which makes sense—we just did a kind of &lt;code&gt;inner join&lt;/code&gt;!). Again, your output will likely be different.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Be careful: the &lt;code&gt;GetAccountsBlocked&lt;/code&gt; call can take a while if the user has a long block list.&lt;br&gt;
You’ll notice that this function accepts a &lt;code&gt;cursor&lt;/code&gt; argument set to &lt;code&gt;true&lt;/code&gt; — this means multiple requests may be required to handle pagination, which can take time depending on how many pages there are.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;Did you enjoy this blog post? Let me know by liking my &lt;em&gt;hello world&lt;/em&gt; post! Now that you’ve got the SDK, it’s super easy:&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;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK.Models.Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK.Models.Feed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ATPClient&lt;/span&gt; &lt;span class="n"&gt;client&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;ATPClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;AuthRequest&lt;/span&gt; &lt;span class="n"&gt;authRequest&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;AuthRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle.bsky.social"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BskyAuthUser&lt;/span&gt; &lt;span class="n"&gt;authUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BskyTimeline&lt;/span&gt; &lt;span class="n"&gt;timeline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAuthorTimeline&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;BskyThread&lt;/span&gt; &lt;span class="n"&gt;helloWorld&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPostThread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"at://did:plc:6rujkdwb4mzzplqzccqmui2h/app.bsky.feed.post/3lojdtvoqb52g"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LikePost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;helloWorld&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you want to go further, many other features are already available in the SDK:&lt;/p&gt;

&lt;p&gt;Like/unlike, follow/unfollow, block/unblock, mute/unmute, and full access to all your timelines.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>atprotocol</category>
    </item>
    <item>
      <title>SDK BlueSky : Utiliser C# pour comprendre les blocages entre utilisateurs</title>
      <dc:creator>Mary 🇪🇺 🇷🇴 🇫🇷</dc:creator>
      <pubDate>Wed, 07 May 2025 07:52:07 +0000</pubDate>
      <link>https://dev.to/pykpyky/sdk-bluesky-utiliser-c-pour-comprendre-les-blocages-entre-utilisateurs-1p2j</link>
      <guid>https://dev.to/pykpyky/sdk-bluesky-utiliser-c-pour-comprendre-les-blocages-entre-utilisateurs-1p2j</guid>
      <description>&lt;p&gt;La plus part des services, réseaux sociaux compris, on des API disponibles pour permettre à des programmes tiers ou des utilisateurs avertis d’effectuer des actions, normalement manuelles et fastidieuses, grâce à du code.&lt;/p&gt;

&lt;p&gt;Imaginons, que je bloque des gens sur BlueSky, et j’aimerais savoir si un autre compte a bloqué les mêmes comptes que moi. Je dois :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prendre ma liste de personnes bloquées&lt;/li&gt;
&lt;li&gt;Prendre la liste des personnes bloquées par l’autre compte&lt;/li&gt;
&lt;li&gt;Et pour chaque nom vérifier s’il est présent dans les deux listes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;C’est pas impossible à faire, sur BlueSky en tous cas car tout est public. Mais c’est extrêmement chiant.&lt;/p&gt;

&lt;p&gt;Autre exemple. Imaginez que je veuille bloquer toutes les personnes qui ont liké un post (Parce que par exemple c’est un post qui vante les mérites de Kubernetes et que je n'aime pas Kubernetes)&lt;/p&gt;

&lt;p&gt;Je dois pour ça :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prendre le post&lt;/li&gt;
&lt;li&gt;Voir la liste des personnes qui ont liké le post&lt;/li&gt;
&lt;li&gt;Bloquer une à une les personnes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hey, on va pas se mentir, c’est chiant comme la pluie à faire !&lt;/p&gt;




&lt;p&gt;Surtout que tous les services ne se valent pas en termes d’API. Pricing excessif à la Twitter (Oui je dis toujours Twitter, tu vas faire quoi Elon ?), extrêmement restrictif à la LinkedIn ou juste parfois non documenté à la Yammer, on pourrait faire un article entier dédié aux différents designs des API.&lt;/p&gt;

&lt;p&gt;Il existe quand même des bons élèves, à propos des APIs, on peut parler de BlueSky. Bon élève principalement car leur application web est basée sur leur propre API. Ce qui aide pas mal à avoir une API propre.&lt;/p&gt;

&lt;p&gt;Mais aussi propre soit l’API BlueSky, ça reste une API, une API REST. Donc un protocole Web. C’est très bien les API REST, ça peut être utilisé par n’importe quel langage (Bash y arrive c’est pour vous dire !)&lt;/p&gt;

&lt;p&gt;Sauf qu'entre les appels asynchrones à gérer et les codes de réponses HTTP qui sont plus longs qu’une liste de courses de fin d’année, ça peut être… embêtant à gérer… proprement.&lt;/p&gt;

&lt;p&gt;Pour masquer l’utilisation des API il est donc possible d’utiliser des SDK. Les SDK sont des bibliothèques pour votre langage qui facilitent l’utilisation d’un service.&lt;/p&gt;

&lt;p&gt;Pour BlueSky vous avez par exemple des SDK TypeScript ou Python (Alors autant je vois l’utilité d’un SDK Python, autant TypeScript, c’est déjà des gens chelous qui font du web, je pense qu’ils ont l’habitude de faire des appels sur des API REST)&lt;/p&gt;

&lt;p&gt;Si par contre vous codez avec autre chose comme du Java ou du C#, vous n’aurez pas le choix que de gérer vos appels REST vous-même.&lt;/p&gt;

&lt;p&gt;Heureusement, mes sharpistes, pour vous pas de galère, on va voir ici comment résoudre nos problèmes en C# !&lt;/p&gt;




&lt;h2&gt;
  
  
  Hello World
&lt;/h2&gt;

&lt;p&gt;Première chose : il nous faut donc un SDK !&lt;br&gt;
Ça tombe bien, sur Nuggets on peut trouver un SDK nommé &lt;code&gt;ATbluePandaSDK&lt;/code&gt;, ce SDK permet d’interagir simplement avec BlueSky en C#.&lt;/p&gt;

&lt;p&gt;Pour l’installer, rien de plus simple : allez dans votre gestionnaire de packages Nugget ou exécutez la commande :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package ATbluePandaSDK &lt;span class="nt"&gt;--version&lt;/span&gt; 0.1.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avant de faire notre use cases super complexe, on va tester quelque chose de simple et basique : un petit Hello World.&lt;/p&gt;

&lt;p&gt;On ouvre notre projet &lt;code&gt;ConsoleApp1&lt;/code&gt; car on n’est pas là pour donner des noms à nos projets, on n’a pas le temps.&lt;/p&gt;

&lt;p&gt;Commençons avec ce code assez simple :&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;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK.Models.Auth&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="n"&gt;ATPClient&lt;/span&gt; &lt;span class="n"&gt;client&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;ATPClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;AuthRequest&lt;/span&gt; &lt;span class="n"&gt;authRequest&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;AuthRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle.bsky.social"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BskyAuthUser&lt;/span&gt; &lt;span class="n"&gt;authUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreatePost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello world from ATbluePandaSDK version 0.1.2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si vous exécutez ce code, vous devriez avoir juste une sortie console disant "Hello world".&lt;br&gt;
Mais là où vous devriez regarder, c’est maintenant sur votre compte BlueSky : vous devriez voir quelque chose comme ceci :&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%2Ft69ixozyc83lnpvux4s7.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%2Ft69ixozyc83lnpvux4s7.png" alt="BlueSky Post saying hello world" width="624" height="229"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Le code est court, mais on va quand même l’expliquer :&lt;/p&gt;

&lt;p&gt;On commence par créer un objet client de type &lt;code&gt;ATPClient&lt;/code&gt;. Ce client, c’est vraiment la classe la plus importante du SDK : tout ce que vous voulez faire sur BlueSky est fait grâce à ce client.&lt;/p&gt;

&lt;p&gt;Mais pour faire des trucs, on doit commencer par se connecter. Pour cela, on va créer un objet &lt;code&gt;AuthRequest&lt;/code&gt; qui reçoit en paramètre votre handle et votre mot de passe.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Mais pour faire des trucs, on doit commencer par se connecter" En fait c'est faux, tous les endpoints ne demandent pas une authentification, mais le post en l’occurrence — et d’autres que l’on va utiliser — le nécessitent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Puis on s’authentifie via la méthode &lt;code&gt;Authenticate&lt;/code&gt; du client &lt;code&gt;ATP&lt;/code&gt; qui prend en paramètre notre &lt;code&gt;AuthRequest&lt;/code&gt; précédemment créé, et qui va nous renvoyer un objet &lt;code&gt;BskyAuthUser&lt;/code&gt;.&lt;br&gt;
Cet objet, c’est vous, votre identité. Il contient vos informations principales comme votre DID, votre handle, mais aussi — et surtout — votre &lt;strong&gt;token&lt;/strong&gt;, qui va permettre d’utiliser d’autres endpoints de l’API BlueSky sans redonner votre mot de passe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ne le donnez à personne !&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finalement, on crée notre post via la fonction &lt;code&gt;CreatePost&lt;/code&gt;, qui prend en paramètre le texte que l’on souhaite poster.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notez qu’on n’a pas utilisé le &lt;code&gt;BskyAuthUser&lt;/code&gt;. On aurait pu, mais le client &lt;code&gt;ATP&lt;/code&gt; le garde en mémoire, et vous pouvez donc l’omettre pour vos prochaines actions.&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;Bravo ! Vous avez fait votre Hello World avec le SDK C# de BlueSky.&lt;br&gt;
Ému ? Attendez, c’est pas fini !&lt;/p&gt;


&lt;h2&gt;
  
  
  Un vrai use case
&lt;/h2&gt;

&lt;p&gt;Ok, vous avez pu tester les premières fonctionnalités du SDK pour vous assurer que tout marche correctement. Sauf que poster sur BlueSky via votre IDE, c’est très cool, mais c’est, je pense, moins user friendly que via l’application web. Alors allons ensemble un peu plus loin avec notre tout premier exemple : &lt;strong&gt;connaître les comptes bloqués par moi-même et un autre utilisateur&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;À quoi cela peut bien servir ? Je ne sais pas, peut-être un futur Tinder qui vous proposera des matchs via une compatibilité basée sur votre pourcentage en commun de gens bloqués !&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Commençons par récupérer notre propre liste des gens qu’on a bloqués et afficher leur nom :&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATbluePandaSDK.Models.Account&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK.Models.Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ATPClient&lt;/span&gt; &lt;span class="n"&gt;client&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;ATPClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;AuthRequest&lt;/span&gt; &lt;span class="n"&gt;authRequest&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;AuthRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle.bsky.social"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BskyAuthUser&lt;/span&gt; &lt;span class="n"&gt;authUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&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;Block&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listOfUsersBlocked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBlocks&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="n"&gt;Block&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;listOfUsersBlocked&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="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;displayName&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;Ce code donnera pour moi cette sortie :&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%2F05w4yzxnj54zcg4sl8pm.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%2F05w4yzxnj54zcg4sl8pm.png" alt="Pleople I blocked on bsky" width="624" height="813"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pour vous bien sûr, il s’agira de vos propres gens bloqués, mais n’ayez pas honte de partager votre liste, de toute façon c’est public ! &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notez que ce sont les gens que &lt;strong&gt;vous avez bloqués personnellement&lt;/strong&gt;, les gens bloqués via des listes de modération ne sont pas affichés ici !&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reprenons ensemble le code à partir de la connexion !&lt;/p&gt;

&lt;p&gt;Avec notre client, nous appelons la fonction &lt;code&gt;GetBlocks&lt;/code&gt; qui retourne une liste de personnes bloquées (Pas de chance pour eux !) — cette fonction s’exécute pour l’utilisateur connecté, donc &lt;strong&gt;nous-même&lt;/strong&gt;, et uniquement nous-même !&lt;/p&gt;

&lt;p&gt;On parcourt cette liste, et dans les différents attributs de notre objet &lt;code&gt;Block&lt;/code&gt;, nous avons par exemple le &lt;code&gt;displayName&lt;/code&gt; (on a d’autres infos, mais pas utiles pour le moment).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Est-ce que &lt;code&gt;Block&lt;/code&gt; est un &lt;code&gt;User&lt;/code&gt; ?&lt;/strong&gt;&lt;br&gt;
Oui, il existe un objet &lt;code&gt;User&lt;/code&gt; qui a beaucoup plus d’informations, mais &lt;code&gt;Block&lt;/code&gt; a la majorité des informations d’un utilisateur, vous pouvez donc passer facilement d’un objet &lt;code&gt;Block&lt;/code&gt; à un objet &lt;code&gt;User&lt;/code&gt; si vous le souhaitez !&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Prochaine étape : obtenir la liste des personnes bloquées par quelqu’un d’autre !&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pour ça, on va devoir utiliser deux autres fonctions du client :&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;using&lt;/span&gt; &lt;span class="nn"&gt;ATbluePandaSDK.Models.Account&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK.Models.Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ATPClient&lt;/span&gt; &lt;span class="n"&gt;client&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;ATPClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;AuthRequest&lt;/span&gt; &lt;span class="n"&gt;authRequest&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;AuthRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle.bsky.social"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BskyAuthUser&lt;/span&gt; &lt;span class="n"&gt;authUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&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;Block&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listOfUsersBlocked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBlocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUserProfil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"otherhandle.bsky.social"&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;Records&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;otherListOfUsersBlocked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAccountsBlocked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&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;Il va nous falloir cette fois un acteur (qui est un objet &lt;code&gt;User&lt;/code&gt;).&lt;br&gt;
On peut l’obtenir de plein de manières. Ici, on a utilisé la fonction &lt;code&gt;GetUserProfil&lt;/code&gt; qui permet d’obtenir toutes les informations d’un utilisateur. Vous pouvez passer en paramètre de cette fonction un DID ou un handle.&lt;/p&gt;

&lt;p&gt;On a donc maintenant &lt;strong&gt;deux listes&lt;/strong&gt; : une de &lt;code&gt;Block&lt;/code&gt; (les utilisateurs que l’on a bloqués), et une de &lt;code&gt;Record&lt;/code&gt; (qui est un terme un peu valise chez BlueSky — mais dans notre cas, ce sont aussi des gens bloqués !).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On ne rentrera pas dans le détail ici de ce qu’est un &lt;code&gt;Record&lt;/code&gt;, ce blog post est déjà beaucoup trop long !&lt;/p&gt;
&lt;/blockquote&gt;



&lt;p&gt;Nous avons donc deux listes, que nous pouvons maintenant croiser facilement car nous sommes en C# et pas en Bash !&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;using&lt;/span&gt; &lt;span class="nn"&gt;ATbluePandaSDK.Models&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATbluePandaSDK.Models.Account&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK.Models.Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ATPClient&lt;/span&gt; &lt;span class="n"&gt;client&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;ATPClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;AuthRequest&lt;/span&gt; &lt;span class="n"&gt;authRequest&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;AuthRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle.bsky.social"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BskyAuthUser&lt;/span&gt; &lt;span class="n"&gt;authUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&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;Block&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;listOfUsersBlocked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBlocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;actor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUserProfil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"otherhandle.bsky.social"&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;Records&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;otherListOfUsersBlocked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAccountsBlocked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&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="n"&gt;Block&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;listOfUsersBlocked&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="n"&gt;Records&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;otherListOfUsersBlocked&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;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;did&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&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="n"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;User&lt;/span&gt; &lt;span class="n"&gt;userBlocked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUserProfil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;did&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="n"&gt;userBlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DisplayName&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="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&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="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Voici ma sortie :&lt;/strong&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%2F9qchb56fiiarotq50a8f.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%2F9qchb56fiiarotq50a8f.png" alt="List of people blocked by myself AND another user" width="599" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On voit que la liste est plus courte qu’avant (c’est normal, c’est une sorte de &lt;code&gt;inner join&lt;/code&gt; que nous avons fait !). Encore une fois, la sortie sera pour vous sans doute différente.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Attention, l’instruction &lt;code&gt;GetAccountsBlocked&lt;/code&gt; peut être longue si la personne a une longue liste de blocages.&lt;br&gt;
Vous voyez en effet que cette fonction a l’argument &lt;code&gt;cursor&lt;/code&gt; à &lt;code&gt;true&lt;/code&gt; — cela veut dire qu’on doit faire plusieurs requêtes pour gérer la pagination, et cela peut prendre du temps selon le nombre de pages.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;Vous avez aimé ce blog post ? N’hésitez pas à le faire savoir en likant mon post &lt;em&gt;hello world&lt;/em&gt;. Maintenant que vous avez un SDK, rien de plus simple :&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;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK.Models.Auth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ATPandaSDK.Models.Feed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;ATPClient&lt;/span&gt; &lt;span class="n"&gt;client&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;ATPClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;AuthRequest&lt;/span&gt; &lt;span class="n"&gt;authRequest&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;AuthRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle.bsky.social"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BskyAuthUser&lt;/span&gt; &lt;span class="n"&gt;authUser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;authRequest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;BskyTimeline&lt;/span&gt; &lt;span class="n"&gt;timeline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAuthorTimeline&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;BskyThread&lt;/span&gt; &lt;span class="n"&gt;helloWorld&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPostThread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"at://did:plc:6rujkdwb4mzzplqzccqmui2h/app.bsky.feed.post/3lojdtvoqb52g"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LikePost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;helloWorld&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et si vous souhaitez aller plus loin, il y a plein d'autres fonctions déjà disponibles dans le SDK :&lt;/p&gt;

&lt;p&gt;Le &lt;em&gt;like/unlike&lt;/em&gt;, mais aussi le &lt;em&gt;follow/unfollow&lt;/em&gt;, &lt;em&gt;block/unblock&lt;/em&gt;, &lt;em&gt;mute/unmute&lt;/em&gt; et l’accès à toutes vos timelines.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>atprotocol</category>
    </item>
    <item>
      <title>AsyncLocal share information between class</title>
      <dc:creator>Mary 🇪🇺 🇷🇴 🇫🇷</dc:creator>
      <pubDate>Mon, 09 Dec 2024 13:21:48 +0000</pubDate>
      <link>https://dev.to/pykpyky/asynclocal-share-information-between-class-4e4d</link>
      <guid>https://dev.to/pykpyky/asynclocal-share-information-between-class-4e4d</guid>
      <description>&lt;p&gt;When you write logs in an application, you likely have a dedicated class (or even a singleton instance) that handles the writing of these logs and that you use throughout your code. This mechanism is usually simple and straightforward. But if you take a look at how the logger is used in PandApache, you might be a bit surprised by this line:&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;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Admin server listening on &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ServerConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServerIP&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ServerConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdminPort&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12/09/2024 12:26:48  - Module: Admin      - Thread ID: 1  - INFO       - Admin server listening on 0.0.0.0:4040
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before explaining this line, let’s take a step back to understand how we got here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does PandApache work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When PandApache is running, you have several elements in play:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Service&lt;/strong&gt;: the main program itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modules&lt;/strong&gt;: each module creates a new task, as they run concurrently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subtasks&lt;/strong&gt;: some modules may start their own subtasks depending on the action they are performing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal is to make each module as independent and configurable as possible. Therefore, each module should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Its own logger&lt;/strong&gt;: this allows you to know which log was written by which module, while applying specific logging rules (different levels, different files, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Its own task scheduler&lt;/strong&gt;: so as not to monopolize all the resources and to leave enough for others.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How to manage the correct logger in each module?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are several ways to ensure that each module uses the correct logger (and the correct task scheduler, but we’ll set that aside for now). One solution would be to pass the appropriate logger as a parameter to each method, or to use dependency injection. This could look like:&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;Logger&lt;/span&gt; &lt;span class="n"&gt;loggerAdmin&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;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configAdmin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;loggerWeb&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;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configWeb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Module&lt;/span&gt; &lt;span class="n"&gt;moduleAdmin&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;Module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loggerAdmin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Module&lt;/span&gt; &lt;span class="n"&gt;moduleWeb&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;Module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loggerWeb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach is not ideal. Why? Because each sub-function would need to access the correct logger, either by passing it explicitly as a parameter or through dependency injection, which can quickly become cumbersome and redundant.&lt;/p&gt;

&lt;p&gt;Let’s take an example in a module:&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;Module&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;Logger&lt;/span&gt; &lt;span class="n"&gt;LoggerAdmin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;LoggerAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Here"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;RandomObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoggerAdmin&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;RandomObject&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; 

    &lt;span class="nf"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"There"&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;You could also opt for a solution with static objects that return the correct logger, which would give:&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="nf"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;LoggerAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Here"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;LoggerAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"There"&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;But this solution has two major problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manually choosing the correct logger&lt;/strong&gt;: For every function or class, you have to decide which logger to use. And if a function changes context (for example, from the Web module to the Admin module), you constantly need to adapt this choice. This lacks flexibility and increases the risk of bugs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Handling multiple instances&lt;/strong&gt;: The Web and Admin modules are actually two instances of the same module, but running the same code. How do you ensure each instance has its own logger without specifying it in every call?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The real challenge is to find a way to automatically retrieve the correct logger (or any other module-specific object) without having to specify it each time and without using specific parameters. This object must be determined based on the module that is currently running, and not manually at each function call.&lt;/p&gt;

&lt;p&gt;With that in mind, how could you solve this problem more elegantly? Leave a comment to share your approach and explain how you would handle dependency injection in this context, particularly for module-specific objects like the logger.&lt;/p&gt;




&lt;h2&gt;
  
  
  Virtual Logger
&lt;/h2&gt;

&lt;p&gt;To fully understand the final solution, it's important to have the desired logs in mind. Here are the PandApache logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12/09/2024 12:33:25  - Module: Server     - Thread ID: 1  - WARNING    - Module Telemetry disabled
12/09/2024 12:33:25  - Module: Server     - Thread ID: 1  - INFO       - PandApache3 is starting
12/09/2024 12:33:25  - Module: Web        - Thread ID: 1  - INFO       - Starting Connection manager module
12/09/2024 12:33:25  - Module: Web        - Thread ID: 1  - INFO       - Web server listening on 0.0.0.0:8080
12/09/2024 12:33:25  - Module: Admin      - Thread ID: 1  - INFO       - Starting Connection manager module
12/09/2024 12:33:25  - Module: Admin      - Thread ID: 1  - INFO       - Admin server listening on 0.0.0.0:4040
12/09/2024 12:33:25  - Module: default    - Thread ID: 1  - INFO       - PandApache3 process id:3738
12/09/2024 12:33:25  - Module: default    - Thread ID: 1  - INFO       - PandApache3 process name:dotnet
12/09/2024 12:33:25  - Module: Server     - Thread ID: 1  - INFO       - PandApache3 is up and running!
12/09/2024 12:33:25  - Module: Web        - Thread ID: 6  - INFO       - Running Connection manager module
12/09/2024 12:33:28  - Module: Web        - Thread ID: 6  - INFO       - Client connected
12/09/2024 12:33:28  - Module: Web        - Thread ID: 12 - INFO       - Reading query string parameter
12/09/2024 12:33:28  - Module: Web        - Thread ID: 13 - INFO       - LoggerMiddleware invoked
12/09/2024 12:33:28  - Module: Web        - Thread ID: 13 - INFO       - Log Request
12/09/2024 12:33:28  - Module: Web        - Thread ID: 13 - INFO       - [12/09/2024 10:33:28] GET /
12/09/2024 12:33:28  - Module: Web        - Thread ID: 14 - INFO       - Log Response
12/09/2024 12:33:28  - Module: Web        - Thread ID: 14 - INFO       - [12/09/2024 10:33:28] Response status code: 200
12/09/2024 12:33:28  - Module: Web        - Thread ID: 14 - INFO       - client Closed
12/09/2024 12:33:28  - Module: Web        - Thread ID: 6  - INFO       - Client connected
12/09/2024 12:33:28  - Module: Web        - Thread ID: 13 - INFO       - Reading query string parameter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These logs show typical information, but two elements that change regularly and are very important are the module and thread ID.&lt;/p&gt;

&lt;p&gt;To understand how we got to these logs, it's logical to first look at the components of a PandApache module, particularly its properties. Here are the properties of the &lt;code&gt;ConnectionManager&lt;/code&gt; module, which is the class instantiated for both the Web and Admin modules:&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="n"&gt;TcpListener&lt;/span&gt; &lt;span class="n"&gt;Listener&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;TaskFactory&lt;/span&gt; &lt;span class="n"&gt;TaskFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ModuleConfiguration&lt;/span&gt; &lt;span class="n"&gt;ModuleInfo&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;ModuleType&lt;/span&gt; &lt;span class="n"&gt;ModuleType&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;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;AsyncLocal&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ModuleConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_current&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;AsyncLocal&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ModuleConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;CancellationTokenSource&lt;/span&gt; &lt;span class="n"&gt;_cancellationTokenSource&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;new&lt;/span&gt; &lt;span class="nf"&gt;CancellationTokenSource&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;ConcurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ISocketWrapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_clients&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;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="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ISocketWrapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="k"&gt;private&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="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ISocketWrapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_clientsRejected&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="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ISocketWrapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_pipeline&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;TaskScheduler&lt;/span&gt; &lt;span class="n"&gt;_taskScheduler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The properties that will interest us are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public ModuleConfiguration ModuleInfo { get; set; }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;private TaskScheduler _taskScheduler;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;TaskScheduler&lt;/code&gt; is quite simple, although we have re-implemented our own &lt;code&gt;TaskScheduler&lt;/code&gt; for a few modifications. It's an inheritance of the default class, so it functions in a similar way.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ModuleConfiguration&lt;/code&gt;, however, is an original class that looks 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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ModuleConfiguration&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;TaskScheduler&lt;/span&gt; &lt;span class="n"&gt;_taskScheduler&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;ModuleType&lt;/span&gt; &lt;span class="n"&gt;Type&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="n"&gt;Name&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;bool&lt;/span&gt; &lt;span class="n"&gt;isEnable&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;TaskFactory&lt;/span&gt; &lt;span class="n"&gt;TaskFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;VirtualLogger&lt;/span&gt; &lt;span class="n"&gt;Logger&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;ModuleConfiguration&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;name&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;moduleType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Logger&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;VirtualLogger&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="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;We can see the &lt;code&gt;TaskScheduler&lt;/code&gt; again, but we also have a &lt;code&gt;VirtualLogger&lt;/code&gt; that takes a name as a parameter.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;VirtualLogger&lt;/code&gt; behaves exactly like the normal PandApache logger. In fact, both the &lt;code&gt;VirtualLogger&lt;/code&gt; and &lt;code&gt;Logger&lt;/code&gt; classes implement the &lt;code&gt;ILogger&lt;/code&gt; interface.&lt;/p&gt;

&lt;p&gt;The difference is that the &lt;code&gt;VirtualLogger&lt;/code&gt; sends its log to the &lt;code&gt;Logger&lt;/code&gt;, and the &lt;code&gt;Logger&lt;/code&gt; sends the log to the system (either the console or a file).&lt;/p&gt;

&lt;p&gt;So, we have modules running in their own tasks, each containing the two instances of objects that we want to use in very specific execution contexts. We now have all the components in place for what we want to achieve. Let's see how this works.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Between us&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One might say that a &lt;code&gt;VirtualLogger&lt;/code&gt; is an unnecessary abstraction here. We could easily use the original &lt;code&gt;Logger&lt;/code&gt; class with different instances, just like we do with the &lt;code&gt;VirtualLogger&lt;/code&gt;. This is true, but there are other benefits to using the &lt;code&gt;VirtualLogger&lt;/code&gt; that haven't been explained here. What you need to take away is that the &lt;code&gt;VirtualLogger&lt;/code&gt; is not just there to have independent loggers, but primarily to separate the action of logging information and the action of distributing it, whether to the console or to a file. We’ll explore this in a future blog post.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Execution Context
&lt;/h2&gt;

&lt;p&gt;Let’s revisit the line to execute the logger from earlier.&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;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Admin server listening on &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ServerConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServerIP&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ServerConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdminPort&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s time to talk about the &lt;code&gt;ExecutionContext&lt;/code&gt; object. It’s a simple class that contains just a static field for easy access everywhere. Here it is:&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;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExecutionContext&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;AsyncLocal&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ModuleConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_current&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;AsyncLocal&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ModuleConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ModuleConfiguration&lt;/span&gt; &lt;span class="n"&gt;Current&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;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&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;The special feature of this class is the type of &lt;code&gt;_current&lt;/code&gt;, which is an &lt;code&gt;AsyncLocal&amp;lt;ModuleConfiguration&amp;gt;&lt;/code&gt;. This ensures that &lt;code&gt;_current&lt;/code&gt; has a distinct value across different execution contexts, meaning, across tasks.&lt;/p&gt;

&lt;p&gt;Each module, at startup, assigns its &lt;code&gt;ModuleInfo&lt;/code&gt; object to the &lt;code&gt;_current&lt;/code&gt; field of its execution context:&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;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ModuleInfo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When, within the module, the line:&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;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting Connection manager module"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12/09/2024 12:26:48  - Module: Web        - Thread ID: 1  - INFO       - Starting Connection manager module
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is used, the correct logger for the module is indeed used.&lt;/p&gt;

&lt;p&gt;Here are the two log outputs from the same line, generated by two modules (Web and Admin):&lt;/p&gt;

&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12/09/2024 12:26:48  - Module: Web        - Thread ID: 1  - INFO       - Starting Connection manager module
12/09/2024 12:26:48  - Module: Web        - Thread ID: 1  - INFO       - Web server listening on 0.0.0.0:8080
12/09/2024 12:26:48  - Module: Admin      - Thread ID: 1  - INFO       - Starting Connection manager module
12/09/2024 12:26:48  - Module: Admin      - Thread ID: 1  - INFO       - Admin server listening on 0.0.0.0:4040
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a module launches another task, like the Web module accepting a connection to process a request:&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="nf"&gt;AcceptConnectionsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We theoretically change context, as we are now in a sub-task of the main task. However, the value of the &lt;code&gt;AsyncLocal&lt;/code&gt; property is automatically inherited, which means that in the &lt;code&gt;AcceptConnectionsAsync&lt;/code&gt; function, when we use the logger via &lt;code&gt;ExecutionContext&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Client connected"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12/09/2024 12:33:45  - Module: Admin      - Thread ID: 9  - INFO       - Client connected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We still have the correct &lt;code&gt;ModuleInfo&lt;/code&gt; configured.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finally
&lt;/h2&gt;

&lt;p&gt;All of this allows us to do a lot within PandApache. For the loggers, even though each &lt;code&gt;VirtualLogger&lt;/code&gt; differs very little (only the module name stored directly in the logger varies from one &lt;code&gt;VirtualLogger&lt;/code&gt; to another), we still benefit from transparency and logical isolation when using them, as well as consistency and standardization when writing logs.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;TaskScheduler&lt;/code&gt;, which we’ve spoken little about because it’s less visual but functions exactly like the logger, once again allows us, in a very transparent way, to launch new tasks within our module. It enforces rules, especially regarding the number of custom threads. This ensures, for example, that the telemetry module, which is not a critical module, doesn’t consume too many resources (one thread for telemetry is sufficient, depending on the number of metrics you wish to capture).&lt;/p&gt;

&lt;p&gt;On the other hand, if your web module no longer has enough resources to handle a new request, you’ll be quite frustrated. Ultimately, this shared execution context enables a finer resource management approach.&lt;/p&gt;




&lt;p&gt;I hope this article helped you better understand the AsyncLocal in C#. If you’re interested in this language, know that the PandApache3 code is available on &lt;a href="https://github.com/MarieLePanda/PandApache3" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and live on &lt;a href="https://www.twitch.tv/pykpyky" rel="noopener noreferrer"&gt;Twitch&lt;/a&gt;. Feel free to follow the journey!&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>programming</category>
    </item>
    <item>
      <title>AsyncLocal pour le partage d'informations entre classes</title>
      <dc:creator>Mary 🇪🇺 🇷🇴 🇫🇷</dc:creator>
      <pubDate>Mon, 09 Dec 2024 12:59:38 +0000</pubDate>
      <link>https://dev.to/pykpyky/asynclocal-pour-le-partage-dinformations-entre-classes-3db6</link>
      <guid>https://dev.to/pykpyky/asynclocal-pour-le-partage-dinformations-entre-classes-3db6</guid>
      <description>&lt;p&gt;Quand vous écrivez des logs dans une application, vous avez sûrement une classe dédiée (voire une instance singleton) qui gère l’écriture de ces logs et que vous utilisez partout dans votre code. Ce mécanisme est généralement simple et direct. Mais si vous jetez un œil à la façon dont on utilise le logger dans PandApache, vous pourriez être un peu surpris par cette ligne :&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;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Admin server listening on &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ServerConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServerIP&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ServerConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdminPort&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sortie:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12/09/2024 12:26:48  - Module: Admin      - Thread ID: 1  - INFO       - Admin server listening on 0.0.0.0:4040
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avant d'expliquer cette ligne, prenons un peu de recul pour comprendre comment on en est arrivé là.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comment PandApache fonctionne?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lorsque PandApache est en cours d'exécution, vous avez plusieurs éléments en jeu :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Le Service&lt;/strong&gt; : c'est le programme principal lui-même.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Les modules&lt;/strong&gt; : chaque module crée une nouvelle tâche, car ils s’exécutent simultanément.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Les sous-tâches&lt;/strong&gt; : certains modules peuvent démarrer leurs propres sous-tâches en fonction de l'action qu'ils effectuent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;L'objectif est de rendre chaque module le plus indépendant et configurable possible. Ainsi, chaque module doit posséder :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Son propre logger&lt;/strong&gt; : cela permet de savoir quel log a été écrit par quel module, tout en appliquant des règles de log spécifiques (niveaux différents, fichiers différents, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Son propre task scheduler&lt;/strong&gt; : pour ne pas monopoliser toutes les ressources et laisser suffisamment pour les autres&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;comment gérer le bon logger dans chaque module ?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Il existe plusieurs façons de s'assurer que chaque module utilise le bon logger (et le bon task scheduler, mais on va le laisser de coté pour le moment). Une solution serait de passer le logger approprié en paramètre à chaque méthode, ou d'utiliser l'injection de dépendance. Cela pourrait ressembler à ceci :&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;Logger&lt;/span&gt; &lt;span class="n"&gt;loggerAdmin&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;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configAdmin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;loggerWeb&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;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configWeb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Module&lt;/span&gt; &lt;span class="n"&gt;moduleAdmin&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;Module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loggerAdmin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Module&lt;/span&gt; &lt;span class="n"&gt;moduleWeb&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;Module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loggerWeb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cette approche n'est pas idéale. Pourquoi ? Parce qu'il faudrait que chaque sous-fonction puisse accéder au logger approprié, soit en le passant explicitement en paramètre, soit par injection de dépendance, ce qui peut rapidement devenir lourd et redondant.&lt;/p&gt;

&lt;p&gt;Prenons un exemple dans un module :&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;Module&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;Logger&lt;/span&gt; &lt;span class="n"&gt;LoggerAdmin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;LoggerAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Here"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;RandomObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoggerAdmin&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;RandomObject&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; 

    &lt;span class="nf"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"There"&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;On pourrait aussi opter pour une solution avec des objets statiques qui renverraient le bon logger, ce qui donnerait :&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="nf"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;LoggerAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Here"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;LoggerAdmin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"There"&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;Mais cette solution présente deux problèmes majeurs :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Choisir manuellement le bon logger&lt;/strong&gt; : À chaque fonction ou classe, il faut décider quel logger utiliser. Et si une fonction change de contexte (par exemple, du module Web au module Admin), il faut constamment adapter ce choix. Cela manque de flexibilité et augmente les risques de bugs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;La gestion des instances multiples&lt;/strong&gt; : Les modules Web et Admin sont en réalité deux instances du même module, mais qui exécutent le même code. Comment faire en sorte que chaque instance ait son propre logger sans avoir à le spécifier à chaque appel ?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Le véritable défi est de trouver un moyen de récupérer automatiquement le bon logger (ou tout autre objet spécifique au module) sans avoir à le préciser chaque fois et sans passer par des paramètres spécifiques. Ce bon objet doit être déterminé en fonction du module qui est actuellement en cours d’exécution, et non de manière manuelle à chaque appel de fonction.&lt;/p&gt;

&lt;p&gt;Dans cette optique, comment pourriez-vous résoudre ce problème de manière plus élégante ? Laissez un commentaire pour partager votre approche et expliquer comment vous géreriez l’injection de dépendances dans ce contexte, en particulier pour des objets spécifiques à chaque module comme le logger.&lt;/p&gt;




&lt;h2&gt;
  
  
  Virtual Logger
&lt;/h2&gt;

&lt;p&gt;Pour bien imaginer la solution finale, avoir les logs souhaités en tête est important. Voici les logs de PandApache :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12/09/2024 12:33:25  - Module: Server     - Thread ID: 1  - WARNING    - Module Telemetry disabled
12/09/2024 12:33:25  - Module: Server     - Thread ID: 1  - INFO       - PandApache3 is starting
12/09/2024 12:33:25  - Module: Web        - Thread ID: 1  - INFO       - Starting Connection manager module
12/09/2024 12:33:25  - Module: Web        - Thread ID: 1  - INFO       - Web server listening on 0.0.0.0:8080
12/09/2024 12:33:25  - Module: Admin      - Thread ID: 1  - INFO       - Starting Connection manager module
12/09/2024 12:33:25  - Module: Admin      - Thread ID: 1  - INFO       - Admin server listening on 0.0.0.0:4040
12/09/2024 12:33:25  - Module: default    - Thread ID: 1  - INFO       - PandApache3 process id:3738
12/09/2024 12:33:25  - Module: default    - Thread ID: 1  - INFO       - PandApache3 process name:dotnet
12/09/2024 12:33:25  - Module: Server     - Thread ID: 1  - INFO       - PandApache3 is up and running!
12/09/2024 12:33:25  - Module: Web        - Thread ID: 6  - INFO       - Running Connection manager module
12/09/2024 12:33:28  - Module: Web        - Thread ID: 6  - INFO       - Client connected
12/09/2024 12:33:28  - Module: Web        - Thread ID: 12 - INFO       - Reading query string parameter
12/09/2024 12:33:28  - Module: Web        - Thread ID: 13 - INFO       - LoggerMiddleware invoked
12/09/2024 12:33:28  - Module: Web        - Thread ID: 13 - INFO       - Log Request
12/09/2024 12:33:28  - Module: Web        - Thread ID: 13 - INFO       - [12/09/2024 10:33:28] GET /
12/09/2024 12:33:28  - Module: Web        - Thread ID: 14 - INFO       - Log Response
12/09/2024 12:33:28  - Module: Web        - Thread ID: 14 - INFO       - [12/09/2024 10:33:28] Response status code: 200
12/09/2024 12:33:28  - Module: Web        - Thread ID: 14 - INFO       -  client Closed
12/09/2024 12:33:28  - Module: Web        - Thread ID: 6  - INFO       - Client connected
12/09/2024 12:33:28  - Module: Web        - Thread ID: 13 - INFO       - Reading query string parameter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On y voit des informations classiques, mais deux éléments qui changent de façon régulière et qui sont très importants : le module et le thread ID.&lt;/p&gt;

&lt;p&gt;Pour comprendre comment on en arrive à ces logs, le plus logique est sans doute de commencer par regarder de quoi est composé un module de PandApache, notamment ses propriétés. Voici les propriétés du module &lt;code&gt;ConnectionManager&lt;/code&gt;, qui est la classe instanciée pour le module Web et Admin :&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="n"&gt;TcpListener&lt;/span&gt; &lt;span class="n"&gt;Listener&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;TaskFactory&lt;/span&gt; &lt;span class="n"&gt;TaskFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ModuleConfiguration&lt;/span&gt; &lt;span class="n"&gt;ModuleInfo&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;ModuleType&lt;/span&gt; &lt;span class="n"&gt;ModuleType&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;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;AsyncLocal&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ModuleConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_current&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;AsyncLocal&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ModuleConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;CancellationTokenSource&lt;/span&gt; &lt;span class="n"&gt;_cancellationTokenSource&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;new&lt;/span&gt; &lt;span class="nf"&gt;CancellationTokenSource&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;ConcurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ISocketWrapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_clients&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;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="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ISocketWrapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="k"&gt;private&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="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ISocketWrapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_clientsRejected&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="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ISocketWrapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_pipeline&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;TaskScheduler&lt;/span&gt; &lt;span class="n"&gt;_taskScheduler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Les propriétés qui vont nous intéresser en particulier sont : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public ModuleConfiguration ModuleInfo { get; set; }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;private TaskScheduler _taskScheduler;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le &lt;code&gt;TaskScheduler&lt;/code&gt; est plutôt simple, même si nous avons réimplémenté notre propre &lt;code&gt;TaskScheduler&lt;/code&gt; pour quelques modifications. C’est un héritage de la classe par défaut et donc fonctionne globalement de la même manière.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ModuleConfiguration&lt;/code&gt;, quant à elle, est une classe originale que voici :&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;ModuleConfiguration&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;TaskScheduler&lt;/span&gt; &lt;span class="n"&gt;_taskScheduler&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;ModuleType&lt;/span&gt; &lt;span class="n"&gt;Type&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="n"&gt;Name&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;bool&lt;/span&gt; &lt;span class="n"&gt;isEnable&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;TaskFactory&lt;/span&gt; &lt;span class="n"&gt;TaskFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;VirtualLogger&lt;/span&gt; &lt;span class="n"&gt;Logger&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;ModuleConfiguration&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;name&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;moduleType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Logger&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;VirtualLogger&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="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;On retrouve notre &lt;code&gt;TaskScheduler&lt;/code&gt;, mais nous avons aussi un &lt;code&gt;VirtualLogger&lt;/code&gt; qui prend en paramètre un nom.&lt;/p&gt;

&lt;p&gt;Un &lt;code&gt;VirtualLogger&lt;/code&gt; est exactement comme le logger normal de PandApache. D’ailleurs, la classe &lt;code&gt;VirtualLogger&lt;/code&gt; et &lt;code&gt;Logger&lt;/code&gt; implémentent tous deux l’interface &lt;code&gt;ILogger&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;La différence est que le &lt;code&gt;VirtualLogger&lt;/code&gt; envoie son log au &lt;code&gt;Logger&lt;/code&gt;, et ce dernier envoie son log au système (Console ou fichier).&lt;/p&gt;

&lt;p&gt;Nous avons donc des modules qui s’exécutent chacun dans leur propre tâche et qui contiennent nos deux instances d’objets que l’on souhaite utiliser dans des contextes d’exécution bien précis. Nous avons tous les éléments mis en place pour ce que nous souhaitons faire, il suffit de voir ensemble comment cela fonctionne.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Entre nous&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On pourrait se dire qu’un &lt;code&gt;VirtualLogger&lt;/code&gt; est une abstraction inutile ici. On pourrait très bien utiliser directement la classe &lt;code&gt;Logger&lt;/code&gt; originelle avec plusieurs instances différentes, exactement comme ce que l’on fait avec le &lt;code&gt;VirtualLogger&lt;/code&gt;. C’est certes vrai, mais il y a d'autres avantages au &lt;code&gt;VirtualLogger&lt;/code&gt; qui n’ont pas été expliqués ici. Ce qu’il faut retenir, c’est que le &lt;code&gt;VirtualLogger&lt;/code&gt; n’est pas là uniquement pour avoir des loggers indépendants, mais surtout pour séparer l’action de logger une information et l’action de la distribuer, que ce soit à la console ou dans un fichier. On explorera cela dans un prochain billet de blog.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Le Contexte d'Exécution
&lt;/h2&gt;

&lt;p&gt;Reprenons la ligne pour exécuter le logger du début.&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;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Admin server listening on &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ServerConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServerIP&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ServerConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AdminPort&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Il est temps de parler de l’objet &lt;code&gt;ExecutionContext&lt;/code&gt;. Il s’agit d’une classe simple qui ne contient qu’un champ statique pour être appelé de partout facilement. La voici :&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;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExecutionContext&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;AsyncLocal&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ModuleConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_current&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;AsyncLocal&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ModuleConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ModuleConfiguration&lt;/span&gt; &lt;span class="n"&gt;Current&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;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;value&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;La particularité de cette classe est le type de &lt;code&gt;_current&lt;/code&gt;, qui est un &lt;code&gt;AsyncLocal&amp;lt;ModuleConfiguration&amp;gt;&lt;/code&gt;. Cela nous permet de garantir une valeur distincte de &lt;code&gt;_current&lt;/code&gt; dans les différents contextes d'exécution, autrement dit, dans les tâches.&lt;/p&gt;

&lt;p&gt;Chaque module, au démarrage, assigne à la variable &lt;code&gt;_current&lt;/code&gt; de son contexte d'exécution l’objet &lt;code&gt;ModuleInfo&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ModuleInfo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quand, dans le module, la ligne :&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;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting Connection manager module"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sortie:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12/09/2024 12:26:48  - Module: Web        - Thread ID: 1  - INFO       - Starting Connection manager module
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;est utilisée, c’est donc bien le bon logger qui est utilisé, celui du module.&lt;/p&gt;

&lt;p&gt;D'ailleurs voici les 2 sorties de log de cette même ligne généré par 2 modules (Web et Admin)&lt;/p&gt;

&lt;p&gt;Sortie:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12/09/2024 12:26:48  - Module: Web        - Thread ID: 1  - INFO       - Starting Connection manager module
12/09/2024 12:26:48  - Module: Web        - Thread ID: 1  - INFO       - Web server listening on 0.0.0.0:8080
12/09/2024 12:26:48  - Module: Admin      - Thread ID: 1  - INFO       - Starting Connection manager module
12/09/2024 12:26:48  - Module: Admin      - Thread ID: 1  - INFO       - Admin server listening on 0.0.0.0:4040
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quand un module lance une autre tâche, comme le module Web qui accepte une connexion pour traiter la requête :&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="nf"&gt;AcceptConnectionsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On change théoriquement de contexte, on est dans une sous-tâche de la tâche principale. Cependant, la valeur de la propriété &lt;code&gt;AsyncLocal&lt;/code&gt; est automatiquement héritée, ce qui fait que dans la fonction &lt;code&gt;AcceptConnectionsAsync&lt;/code&gt;, quand on utilise le logger via &lt;code&gt;ExecutionContext&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Client connected"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;sortie:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;12/09/2024 12:33:45  - Module: Admin      - Thread ID: 9  - INFO       - Client connected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nous avons toujours le bon &lt;code&gt;ModuleInfo&lt;/code&gt; configuré.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finalement
&lt;/h2&gt;

&lt;p&gt;Tout ceci nous permet beaucoup de choses dans PandApache. Pour les loggers, même si chaque &lt;code&gt;VirtualLogger&lt;/code&gt; diffère très peu (seul le nom du module stocké directement dans le logger diffère d’un &lt;code&gt;VirtualLogger&lt;/code&gt; à l’autre), nous avons quand même l’avantage de la transparence et de l’isolation logique au moment de l’utilisation, ainsi que de la cohérence et de la standardisation au moment de l’écriture.&lt;/p&gt;

&lt;p&gt;Le &lt;code&gt;TaskScheduler&lt;/code&gt;, dont nous avons très peu parlé car moins visuel, mais qui fonctionne exactement comme le logger, nous permet encore une fois de façon très transparente de lancer de nouvelles tâches dans notre module, en ayant des règles notamment au niveau du nombre de threads personnalisés. Ainsi, on peut s’assurer que le module télémetrie, qui n’est pas un module vital, n’utilise pas trop de ressources (un thread pour la télémetrie est, par exemple, suffisant selon le nombre de métriques que vous souhaitez capturer).&lt;/p&gt;

&lt;p&gt;Au contraire, si votre module web n’a plus assez de ressources pour gérer une nouvelle requête, vous allez être très contrarié. C’est donc une gestion des ressources plus fine qui peut être effectuée au final grâce à ce contexte d’exécution partagé.&lt;/p&gt;




&lt;p&gt;J’espère que cet article vous aura aidé à mieux comprendre l'utilisation de AsyncLocal en C#. Si ce langage vous intéresse, sachez que le code de PandApache3 est disponible sur &lt;a href="https://github.com/MarieLePanda/PandApache3" rel="noopener noreferrer"&gt;GitHub &lt;/a&gt;et en live sur &lt;a href="https://www.twitch.tv/pykpyky" rel="noopener noreferrer"&gt;Twitch&lt;/a&gt;. N’hésitez pas à suivre l’aventure !  &lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>programming</category>
    </item>
    <item>
      <title>Code in music: What the piano can teach you about programming</title>
      <dc:creator>Mary 🇪🇺 🇷🇴 🇫🇷</dc:creator>
      <pubDate>Tue, 03 Dec 2024 12:36:15 +0000</pubDate>
      <link>https://dev.to/pykpyky/code-in-music-what-the-piano-can-teach-you-about-programming-493k</link>
      <guid>https://dev.to/pykpyky/code-in-music-what-the-piano-can-teach-you-about-programming-493k</guid>
      <description>&lt;p&gt;The holiday season is here!&lt;/p&gt;

&lt;p&gt;Like every year in December, there are two things you just can’t escape:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Advent of Code if you’re a programmer.
&lt;/li&gt;
&lt;li&gt;*ingle Bell Rock if you step into a shopping mall.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s just how it is—I don’t make the rules.  &lt;/p&gt;

&lt;p&gt;Advent of Code is all about programming puzzles, kind of like what you find year-round on LeetCode. Its stated goal is to make you a "better programmer." And I do mean programmer, not developer.  &lt;/p&gt;

&lt;p&gt;So, how do you become better at programming? Should you be grinding LeetCode every day? I’ve never had to implement a merge sort at work, so do I really need to learn it?  &lt;/p&gt;

&lt;p&gt;Whoa, slow down!  &lt;/p&gt;

&lt;p&gt;Speaking of which, did you know that piano means "slowly" in Italian? Perfect segue, because before we dive into programming, let’s talk about piano. Don’t leave just yet—it’ll be worth it.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Playing the Piano
&lt;/h2&gt;

&lt;p&gt;When I’m not behind a computer keyboard, you can probably find me behind a piano (keyboard, piano, computer—you see the connection? Alright, got it).  &lt;/p&gt;

&lt;p&gt;And funnily enough, learning piano offers a lot of parallels to programming.  &lt;/p&gt;

&lt;p&gt;What does playing the piano involve? It’s not about memorizing the name or sound of every single key. (You generally know where &lt;em&gt;C&lt;/em&gt; is. From there, based on your hand position relative to &lt;em&gt;C&lt;/em&gt;, you know what notes to play. But you don’t necessarily need to know exactly where &lt;em&gt;A&lt;/em&gt; and &lt;em&gt;B&lt;/em&gt; are, for example.)  &lt;/p&gt;

&lt;p&gt;Playing the piano means hitting the right notes in the right rhythm. It’s really a combination of the two. If you play random notes, you’re just making noise, not music.&lt;br&gt;&lt;br&gt;
If you hit the right notes but leave 10 seconds of silence between each, you don’t have music either—there’s no rhythm.  &lt;/p&gt;

&lt;p&gt;To improve these two skills, there are various exercises. For hitting the right notes, you might play very slow-ly, ignoring rhythm entirely. The goal is to figure out: the note to read on the sheet music and the finger to use to play it. Sometimes your hands will need to move, so you also practice repositioning them. To build muscle memory, you play several notes or melodies slowly—very slow-ly.  &lt;/p&gt;

&lt;p&gt;For rhythm, you play simple notes or sequences. The goal isn’t to play a melody but to hit the notes within the allotted time.  &lt;/p&gt;

&lt;p&gt;As you improve, the exercises become harder, but the ultimate aim remains the same: to eventually play a piece by combining note accuracy and speed to maintain rhythm.  &lt;/p&gt;

&lt;p&gt;When you’re at an intermediate level and play a piece for the first time, if it’s simple (a children’s tune, for example), you can play it almost perfectly on the first try or with minimal practice.&lt;br&gt;&lt;br&gt;
But if you attempt something more challenging (jazz, for instance), you’ll need to do more exercises for note accuracy and rhythm specific to that piece.  &lt;/p&gt;

&lt;p&gt;Another interesting point: you can learn to play a demanding piece, not touch it for a few months, and then try again. You’ll probably be terrible on your first attempt, but you’ll quickly regain your footing and play it perfectly without redoing the entire learning process.  &lt;/p&gt;

&lt;p&gt;If you want to improve at piano, you’ll also need to step out of your comfort zone. You can stick to children’s tunes your entire life, but that won’t help you play jazz later without practice. You need to play different styles, of varying difficulty, and with diverse rhythms.  &lt;/p&gt;

&lt;p&gt;In summary, to learn piano, you need to:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Practice various exercises to improve the different skills required for playing music.
&lt;/li&gt;
&lt;li&gt;Play diverse pieces with increasing difficulty.
&lt;/li&gt;
&lt;li&gt;Occasionally revisit older pieces to quickly regain your footing and strengthen your muscle memory.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  And Programming?
&lt;/h2&gt;

&lt;p&gt;Well, it’s the same.  &lt;/p&gt;

&lt;p&gt;That's it, thank you for your time, see you next soon!&lt;/p&gt;




&lt;h2&gt;
  
  
  And Programming?
&lt;/h2&gt;

&lt;p&gt;Well, it’s the same.  &lt;/p&gt;

&lt;p&gt;Puzzles like LeetCode are like little melodies. To solve them, you need to use the right data structures and algorithms (the equivalent of piano notes) and do so within the given time limit (our rhythm).  &lt;/p&gt;

&lt;p&gt;If you’re tackling a LeetCode puzzle involving a merge sort for the first time, you should start by learning—outside of the puzzle—what a merge sort is, how it works, and how to code it. Slow-ly. Then, you can try solving the puzzle within the time limit. At first, you might exceed the limit, but with practice, you’ll hit the right rhythm.  &lt;/p&gt;

&lt;p&gt;Once you’ve nailed that exercise (the right algorithm, at the right speed), you can explore other puzzles—other melodies. Some will be similar, others not. Increase the difficulty to progress, but don’t expect to succeed on the first try. That’s not the goal.  &lt;/p&gt;

&lt;p&gt;Revisit them a few months later (for a project or an interview). You’ll find that while your implementation might be a bit slow at first, you’ll quickly regain your footing and solve the puzzle within the time limit again.  &lt;/p&gt;




&lt;h2&gt;
  
  
  I’m a Pianist, Not a Musician; I’m a Programmer, Not a Developer
&lt;/h2&gt;

&lt;p&gt;Now, do coding puzzles make you a better programmer? If you do them regularly, probably yes. But do they make you a better developer? Not really, because that’s not the same thing.  &lt;/p&gt;

&lt;p&gt;If I play piano, I can play Jingle Bell Rock at home without issue. Great, I have a good time, and it adds a festive vibe. But playing piano doesn’t teach me to:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compose my own music.
&lt;/li&gt;
&lt;li&gt;Play with other instruments.
&lt;/li&gt;
&lt;li&gt;Perform in an orchestra...
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, lots of things. Playing piano doesn’t necessarily make me a musician. I’m a pianist—a hobbyist pianist, and that’s perfectly fine.  &lt;/p&gt;

&lt;p&gt;Likewise, being good at programming doesn’t make you a developer (the profession). It doesn’t teach you to structure your code, write documentation, or do many other things engineers need. It simply teaches you to solve programming puzzles.  &lt;/p&gt;

&lt;p&gt;Sure, some problems might be reusable in a professional setting (I work with tree traversals like DFS and BFS quite often, for instance), but that’s anecdotal compared to everything else I do on the job.  &lt;/p&gt;




&lt;p&gt;Happy holidays! If you’ve got Jingle Bell Rock stuck in your head now, you’re welcome. 🎄&lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Code en musique: Ce que le Piano peut vous apprendre sur la programmation</title>
      <dc:creator>Mary 🇪🇺 🇷🇴 🇫🇷</dc:creator>
      <pubDate>Tue, 03 Dec 2024 12:00:16 +0000</pubDate>
      <link>https://dev.to/pykpyky/code-en-musique-ce-que-le-piano-peut-vous-apprendre-sur-la-programmation-584</link>
      <guid>https://dev.to/pykpyky/code-en-musique-ce-que-le-piano-peut-vous-apprendre-sur-la-programmation-584</guid>
      <description>&lt;p&gt;La période de fête arrive, comme chaque année en décembre, vous ne pouvez pas échapper à deux choses :  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;L’AventOfCode si vous êtes développeur.
&lt;/li&gt;
&lt;li&gt;Jingle Bell Rock si vous allez dans un centre commercial.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;C’est comme ça, ce n’est pas moi qui fais les règles.  &lt;/p&gt;

&lt;p&gt;L’AventOfCode, ce sont des puzzles de programmation, un peu comme ce qu’on peut retrouver toute l’année sur LeetCode. Le but affiché est de devenir un « meilleur programmeur ». Et je dis bien programmeur, pas développeur.  &lt;/p&gt;

&lt;p&gt;Alors, comment devenir meilleur en programmation ? Est-ce que je dois faire du LeetCode tous les jours ? Je n’ai jamais eu à réimplémenter un merge sort au boulot, dois-je vraiment apprendre à le faire ?  &lt;/p&gt;

&lt;p&gt;WOW, doucement !  &lt;/p&gt;

&lt;p&gt;Tiens, d’ailleurs, vous savez que piano veut dire doucement en italien ? Ça tombe bien, avant de parler de programmation, on va parler de piano. Mais restez, ça va être bien.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Jouer du piano
&lt;/h2&gt;

&lt;p&gt;Quand je ne suis pas derrière un clavier d’ordinateur, vous pouvez sans doute me trouver derrière un piano (clavier, piano, ordinateur, vous l’avez ? Ok, vous l’avez).  &lt;/p&gt;

&lt;p&gt;Et figurez-vous qu’en apprenant le piano, on peut faire pas mal de comparaisons avec la programmation.  &lt;/p&gt;

&lt;p&gt;Jouer du piano, qu’est-ce que c’est ? Ce n’est pas connaître le nom ou le son de chaque touche par cœur. (On sait en général où se trouve le &lt;em&gt;do&lt;/em&gt;. Ensuite, par la position des mains par rapport à la note &lt;em&gt;do&lt;/em&gt;, on sait quelle note jouer. Mais on n’est pas obligé de savoir exactement où se trouvent le &lt;em&gt;la&lt;/em&gt; et le &lt;em&gt;si&lt;/em&gt;, par exemple).  &lt;/p&gt;

&lt;p&gt;Jouer du piano, c’est réussir à enchaîner les bonnes notes, dans le bon rythme. C’est vraiment la combinaison des deux. Si vous enchaînez plein de notes aléatoires, vous faites du bruit, mais pas de la musique.&lt;br&gt;&lt;br&gt;
Si vous enchaînez les bonnes notes, mais qu’il y a 10 secondes de silence entre chaque, on n’a pas de musique non plus, car il n’y a pas de rythme.  &lt;/p&gt;

&lt;p&gt;Pour travailler ces deux compétences, on a plusieurs exercices. Pour les bonnes notes, on va, par exemple, jouer très len-te-ment, hors rythme donc, car le but est d’identifier : la note à lire sur la partition et le doigt à utiliser pour jouer cette note. Sachant que parfois, les mains se déplacent, il faut donc aussi s’entraîner à repositionner ces mains. Pour obtenir cette mémoire musculaire, on va jouer plusieurs notes ou mélodies de façon lente, voire très len-te.  &lt;/p&gt;

&lt;p&gt;Pour travailler le rythme, on va jouer des notes ou des enchaînements simples. Car le but n’est pas tant de jouer une mélodie que de jouer des notes dans le temps imparti.  &lt;/p&gt;

&lt;p&gt;Et bien sûr, plus on progresse, plus les exercices deviennent difficiles, mais le but final est toujours le même : être prêt, à un moment, pour jouer une musique en combinant justesse des notes et rapidité pour garder le rythme.  &lt;/p&gt;

&lt;p&gt;Quand vous avez un niveau moyen au piano, et que vous jouez pour la première fois une musique, si elle est facile (une comptine pour enfants, par exemple), vous pourrez la jouer quasiment parfaitement du premier coup ou avec très peu d’entraînement.&lt;br&gt;&lt;br&gt;
À l’inverse, si vous essayez une musique plus complexe (du jazz, par exemple), vous allez devoir faire plus d’exercices de justesse des notes et de rythme en lien avec cette musique en particulier.  &lt;/p&gt;

&lt;p&gt;Autre fait intéressant : vous pouvez apprendre à jouer une musique exigeante, ne plus y toucher pendant quelques mois et tenter de la jouer à nouveau. Vous serez probablement très mauvais au premier essai, mais très vite, vous retrouverez vos marques et serez capable de la rejouer parfaitement sans refaire toute la phase d’apprentissage.  &lt;/p&gt;

&lt;p&gt;Si vous voulez progresser au piano, vous allez aussi devoir sortir de votre zone de confort. Vous pouvez jouer des comptines pour enfants toute votre vie, mais cela ne vous aidera pas à jouer du jazz ensuite sans entraînement. Il faut donc jouer différents styles, de différents niveaux, avec des rythmes variés.  &lt;/p&gt;

&lt;p&gt;Bref, pour apprendre à jouer du piano, vous devez :  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faire différents exercices pour améliorer les différentes compétences nécessaires pour jouer de la musique.
&lt;/li&gt;
&lt;li&gt;Jouer des morceaux variés avec une difficulté crescendo.
&lt;/li&gt;
&lt;li&gt;Rejouer de temps en temps des musiques pour reprendre vos marques rapidement et travailler votre mémoire musculaire.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Et la programmation dans tout cela ?
&lt;/h2&gt;

&lt;p&gt;Eh bien, pareil.  &lt;/p&gt;

&lt;p&gt;Voila, merci de m'avoir lu, a la semaine prochaine&lt;/p&gt;




&lt;h2&gt;
  
  
  Et la programmation dans tout cela ?
&lt;/h2&gt;

&lt;p&gt;Les puzzles type LeetCode, par exemple, sont de petites mélodies. Pour les réussir, il faut à chaque fois utiliser les bonnes structures de données et les bons algorithmes (c’est l’équivalent des notes de piano), et le faire dans le temps imparti (c’est notre rythme).  &lt;/p&gt;

&lt;p&gt;Si vous vous lancez pour la première fois sur un LeetCode qui demande d’effectuer un merge sort, vous devriez d’abord, hors puzzle, aller apprendre ce qu’est un merge sort, comment il fonctionne, comment le coder. Len-te-ment. Ensuite, vous pouvez tenter de le faire le puzzle dans le temps imparti. Peut-être qu’au début, vous serez hors délai, mais à force d’entraînement, vous aurez le bon rythme.  &lt;/p&gt;

&lt;p&gt;Une fois cet exercice réussi (le bon algo, dans le bon temps), vous pouvez aller explorer d’autres puzzles, d’autres mélodies. Certains seront proches, d’autres moins. Augmentez la difficulté pour progresser, mais ne vous attendez pas à les réussir du premier coup. Ce n’est pas l’objectif.  &lt;/p&gt;

&lt;p&gt;Revenez dessus quelques mois plus tard (pour un projet ou pour un entretien). Vous verrez que, même si l’implémentation sera un peu plus longue au début, vous reprendrez très vite vos marques et réussirez le puzzle dans le temps imparti, très rapidement.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Je suis pianiste, pas musicien ; je suis programmeur, pas développeur
&lt;/h2&gt;

&lt;p&gt;Maintenant, est-ce que les puzzles de code aident à devenir un meilleur programmeur ? Si vous en faites régulièrement, oui, sûrement. Mais est-ce que cela fait de vous un meilleur développeur ? Pas vraiment, car ce n’est pas la même chose.  &lt;/p&gt;

&lt;p&gt;Si je joue du piano, je peux jouer Jingle Bell Rock chez moi sans problème. Super, je passe du bon temps, et ça ajoute à l’atmosphère un parfum de fête. Cependant, jouer du piano ne m’apprend pas à :  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Écrire mes propres musiques.
&lt;/li&gt;
&lt;li&gt;Jouer avec d’autres instruments.
&lt;/li&gt;
&lt;li&gt;Jouer dans un orchestre…
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bref, plein de choses. Jouer du piano ne fait pas forcément de moi un musicien. Je suis pianiste, pianiste amateur, c’est tout, et c’est déjà très bien.  &lt;/p&gt;

&lt;p&gt;Eh bien, être fort en programmation, c’est pareil. Ça ne vous apprend pas à être développeur (le métier). Ça ne vous apprend pas à structurer votre code, à écrire de la documentation ou à faire plein d’autres choses dont un ingénieur a besoin. Cela vous apprend seulement à résoudre des puzzles de programmation.  &lt;/p&gt;

&lt;p&gt;Alors oui, parfois ces problèmes peuvent être réutilisés dans un cadre professionnel (je parcours très souvent des arbres au boulot, par exemple, avec du DFS, du BFS), mais ça reste plutôt anecdotique par rapport à toutes les autres parties de mon job.  &lt;/p&gt;




&lt;p&gt;Joyeuse fête, si vous avez Jingle Bell dans la tête, c'est normal&lt;/p&gt;

</description>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>PandApache version 3.4 est la !</title>
      <dc:creator>Mary 🇪🇺 🇷🇴 🇫🇷</dc:creator>
      <pubDate>Mon, 02 Dec 2024 12:58:16 +0000</pubDate>
      <link>https://dev.to/pykpyky/pandapache-version-34-est-la--13k9</link>
      <guid>https://dev.to/pykpyky/pandapache-version-34-est-la--13k9</guid>
      <description>&lt;p&gt;Ça a mis un peu de temps (C’est ce qui arrive quand un dev backend travaille sur du front), mais ça y est, PandApache3.4 est disponible ! &lt;a href="https://github.com/MarieLePanda/PandApache3/releases" rel="noopener noreferrer"&gt;Release&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cette nouvelle version apporte beaucoup de nouveautés que nous allons voir ensemble maintenant.&lt;/p&gt;

&lt;p&gt;Après la version 3.3, qui était très axée sur la partie administration, PandApache continue sur le chemin d’être un serveur web fait pour et par un SRE. Pas de fonctionnalités pour les utilisateurs finaux, mais juste pour nous, les administrateurs ! Au programme : télémétrie, monitoring et opérations d’administration simplifiées.&lt;/p&gt;

&lt;p&gt;C’est parti !&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture modulaire
&lt;/h2&gt;

&lt;p&gt;Pour un développement et une administration plus simples, le service PandApache3 est maintenant découpé en modules. Trois précisément au moment de cette version :  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Le module Web
&lt;/li&gt;
&lt;li&gt;Le module Admin
&lt;/li&gt;
&lt;li&gt;Le module Télémétrie
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le module Web est tout simplement le cœur de PandApache, il s’agit du module qui sert à écouter sur le port 80, c’est le module qui permet de rediriger chaque requête HTTP vers la bonne ressource.&lt;/p&gt;

&lt;p&gt;Le module Admin n’est pas nouveau, c’est toujours le deuxième service web qui tourne par défaut sur le port 4040 et qui permet d’exécuter des endpoints afin de réaliser des tâches d’administration, comme arrêter ou redémarrer le serveur.&lt;/p&gt;

&lt;p&gt;Le module Télémétrie, lui, est nouveau. On en parlera un peu plus tard, mais c’est un module qui permet de recueillir en temps réel des informations sur votre système afin de s’assurer par exemple que votre système hôte a toujours assez de mémoire, assez de CPU, vérifier les écritures sur le disque, etc.&lt;/p&gt;

&lt;p&gt;Cette nouvelle architecture est très importante pour plusieurs raisons : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplifier grandement le développement dans le futur grâce à cette approche plus modulaire. &lt;/li&gt;
&lt;li&gt;Diagnostiquer des problèmes plus finement, notamment grâce aux logs séparés. Vous pouvez mettre le module Web en mode debug et garder les autres en mode info ou erreur pour ne pas polluer vos logs. &lt;/li&gt;
&lt;li&gt;En cas de problème sur un module, il peut simplement être désactivé, tout en continuant à profiter des autres modules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ces modules sont indépendants et peuvent donc être activés ou désactivés comme vous le souhaitez. &lt;/p&gt;

&lt;p&gt;En vrai, c’est un peu bête d’activer le module Télémétrie sans le module Admin par exemple, car vous n’aurez pas accès aux endpoints pour recueillir les infos de télémétrie. Et ça serait très bizarre de ne pas avoir de module Web sur un serveur web, mais après vous faites ce que vous voulez, hein.&lt;/p&gt;

&lt;p&gt;Par contre, vous pouvez très bien n’avoir que le module Web, ou que le module Web et Admin. Les endpoints de télémétrie ne fonctionneront pas, mais le reste marchera sans problème.&lt;/p&gt;

&lt;p&gt;Ils ont aussi leur propre fichier de log, ce qui permet un troubleshooting plus efficace, en plus d'avoir leur propre niveau de log. &lt;/p&gt;

&lt;p&gt;Voici un exemple de la configuration des modules :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Module&lt;/span&gt; &lt;span class="err"&gt;Telemetry&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    ModuleLogLevel error
    enable true
    ModuleLogFile telemetry.log
&lt;span class="nt"&gt;&amp;lt;/Module&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Module&lt;/span&gt; &lt;span class="err"&gt;Web&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    enable true
    ModuleLogLevel debug
    ModuleLogFile web.log
&lt;span class="nt"&gt;&amp;lt;/Module&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Module&lt;/span&gt; &lt;span class="err"&gt;Admin&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    enable true
    ModuleLogFile admin.log
&lt;span class="nt"&gt;&amp;lt;/Module&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Entre nous :&lt;/em&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;le serveur lui-même est visible comme un module, on peut donc avoir dans les logs des entrées qui viennent du module « Serveur ». C’est tout simplement le processus principal qui lance tous les autres modules et lui ne peut bien sûr pas être désactivé.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Toujours entre nous :&lt;/em&gt;  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;La télémétrie étant quelque chose de très spécifique à l’OS, mais aussi à la langue, elle ne fonctionne que sur Windows. Cependant, le gros du travail est fait pour pouvoir itérer sur cette première base de modules et permettre dans le futur un monitoring plus large sur les différents systèmes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Télémétrie
&lt;/h2&gt;

&lt;p&gt;Vous le savez, la télémétrie pour assurer la fiabilité d’un service est essentielle, aussi bien pour le service lui-même que pour son hôte. C’est pourquoi un nouveau endpoint &lt;code&gt;/admin/monitor&lt;/code&gt; a vu le jour. Sous ce endpoint, vous allez pouvoir retrouver plusieurs métriques afin de vérifier le bon fonctionnement de votre service et de votre système. Retrouvez notamment les endpoints suivants :  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/monitor/cpu&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/monitor/availablememory&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/monitor/diskread&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/monitor/diskwrite&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/monitor/diskqueue&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/monitor/gcheapsize&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Et d'autres encore.  &lt;/p&gt;

&lt;p&gt;Ces endpoints vous permettent de connaître en temps réel les valeurs de votre système correspondant à la métrique associée.&lt;/p&gt;

&lt;p&gt;Voici par exemple le résultat du endpoint : &lt;code&gt;http://127.0.0.1:4040/admin/monitor/cpu&lt;/code&gt; : &lt;code&gt;72,80516052246094&lt;/code&gt;. Correspondant à la valeur actuelle du CPU. &lt;/p&gt;

&lt;p&gt;Bien sûr, cette métrique change à chaque fois. Voici par exemple 3 valeurs différentes obtenues à la suite :  &lt;/p&gt;

&lt;p&gt;&lt;code&gt;72,80516052246094&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;64,66727752685547&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;55,4402359008789&lt;/code&gt;  &lt;/p&gt;

&lt;p&gt;Pour ne pas demander à votre système de monitoring de venir récupérer chaque nouvelle valeur chaque seconde. Il y a un dernier endpoint fonctionnant avec un paramètre: &lt;code&gt;/monitor/metric&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
Le paramètre &lt;code&gt;name&lt;/code&gt; peut être utilisé avec les valeurs suivantes :  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CpuUsagePercentage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AvailableMemoryMB&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DiskReadBytesPerSecond&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DiskWriteBytesPerSecond&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DiskQueueLength&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GCCollectionCount&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GCHeapSizeBytes&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ce endpoint renverra, contrairement aux autres, une réponse au format JSON :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:57:30.1295087+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;57.67658710479736&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="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:57:38.7241797+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;59.110374450683594&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="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:58:08.2192014+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;62.357760747273765&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="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:58:12.4449679+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;54.44178104400635&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="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:58:21.103619+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;65.64530436197917&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="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:58:21.1044157+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;76.43288993835449&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="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:58:29.665167+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;64.24466037750244&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="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:58:59.2308917+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;74.4074338277181&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="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:59:03.3638932+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;78.21070289611816&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="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:59:11.9931579+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;73.42640749613444&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="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:59:11.993631+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;84.76824760437012&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="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:59:20.7471495+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;92.40722846984863&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="nl"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2024-12-01T23:59:29.3142203+02:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;65.4045352935791&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;Cela permet d’obtenir plusieurs valeurs sur un temps plus long.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Entre nous :&lt;/em&gt;&lt;br&gt;
chaque métrique est capturée toutes les 30 secondes en faisant une moyenne des valeurs obtenues, et les 10 dernières métriques vous sont renvoyées ensuite sous un format qui permet facilement de visualiser l'information sur un graphique.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  La console d’administration
&lt;/h2&gt;

&lt;p&gt;Souvenez-vous, lors de la précédente version, la 3.3, nous expliquions que des endpoints administratifs pouvaient être utilisés pour administrer facilement, de manière programmatique, un ou plusieurs services PandApache3, mais aussi pour construire une console d’administration si nous le souhaitions.&lt;/p&gt;

&lt;p&gt;C’est désormais chose faite avec la version 3.4, bienvenue dans la console d’administration de PandApache !&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%2Fawzj4cnkvwjuo7x1w3s7.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%2Fawzj4cnkvwjuo7x1w3s7.png" alt="Image de la console admin" width="624" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Il n’y a rien de plus dur et chiant pour un dev backend que de faire une interface graphique, alors je remercie chaleureusement l’équipe &lt;a href="https://adminlte.io/" rel="noopener noreferrer"&gt;d'AdminLTE &lt;/a&gt; pour leur template de console d’administration. C’est leur travail qui a été repris pour réaliser une console fonctionnelle et vous allez voir, que leur template envoie du pâté !&lt;/p&gt;

&lt;p&gt;La console d’administration dispose, pour le moment, de 4 sous-sections :&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Status :&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Une page pour du troubleshooting rapide. Elle permet de connaître le statut de votre service, ainsi que d’avoir un bref historique des derniers logs, dont le format a d’ailleurs été retravaillé.  &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%2Fsiozsr1obkcg80xfqdny.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%2Fsiozsr1obkcg80xfqdny.png" alt="Image de la page status" width="624" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En plus de l’information du module qui emet les logs, vous avez également son thread ID. Toute la partie gestion de tache de PandApache a été revue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Health :&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Une page avec les métriques de PandApache. Comme vu dans la partie télémétrie, les métriques sont maintenant disponibles via API. Nous en avons donc fait un joli dashboard pour voir en temps réel ce qui se passe.  &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%2Fznicm1p55dfce6xnoe3u.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%2Fznicm1p55dfce6xnoe3u.png" alt="Image de la page health" width="624" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Encore une fois, merci à AdminLTE pour leur travail qui a permis une intégration assez facile !)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Management :&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Cette page permet d’effectuer des opérations sur le service PandApache3, et vous allez voir qu’il n’y a pas beaucoup de limites à ce que vous pouvez y faire !&lt;/p&gt;

&lt;p&gt;Il y a bien sûr les opérations de base qui étaient déjà implémentées : Recharger la configuration, redémarrer le service ou l’arrêter.  &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%2Fdywy9hj61bzuyxaxt40q.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%2Fdywy9hj61bzuyxaxt40q.png" alt="Image de la page management" width="624" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mais vous pouvez aussi maintenant avoir des scripts personnalisés exécutés directement par le service !  &lt;/p&gt;

&lt;p&gt;Les scripts exécutables sont ceux présents dans votre dossier &lt;code&gt;admin&lt;/code&gt;. Nous listons tous les scripts PowerShell ou Shell selon la plateforme.  &lt;/p&gt;



&lt;p&gt;Vous pouvez, via un nouveau endpoint, les exécuter et obtenir le résultat du script.  &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%2Fywyeltuomk3valcc7c3v.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%2Fywyeltuomk3valcc7c3v.png" alt="Image d’un script en cours d’exécution" width="624" height="105"&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%2F8qz9fky81hs8t1qib65m.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%2F8qz9fky81hs8t1qib65m.png" alt="Image d’un script exécuté avec succès" width="624" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Avant de faire une syncope en pensant qu’il est possible d’exécuter du code à distance, sachez que cette option est bien sûr désactivable dans la configuration. Mais les vrais savent à quel point c’est une fonctionnalité importante pour la bonne maintenance d’un service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuration :&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Enfin, la dernière page vous affiche la configuration actuelle au format JSON. &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%2Fq1l1ilfklnouulwsmb7i.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%2Fq1l1ilfklnouulwsmb7i.png" alt="Image de la page configuration" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bien que le fichier de configuration au format JSON ne soit pas encore implémenté, cela vous donne un avant-goût du futur : plus de formats de configuration supportés et une gestion de la configuration et de ses changements facilitée.&lt;/p&gt;

&lt;p&gt;_Entre nous : _&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;L’exécution des scripts et leurs résultats est, pour le moment, synchrone. Ce n’est pas idéal, notamment car certains scripts peuvent être longs à s’exécuter. Un système asynchrone avec un task ID qui vous sera renvoyé pour connaître l’état de votre script va être proposé dans le futur, toujours pour faciliter au mieux l’administration du service.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Pour finir
&lt;/h2&gt;

&lt;p&gt;C’était une présentation rapide des nouvelles fonctionnalités clés de PandApache 3.4. J’espère que ce tour d’horizon vous a plu. Cette nouvelle version vient avec, bien sûr, plein de changements non visibles (gestion des logs, gestion des threads, etc.), mais bien réels et qui seront le sujet de futurs articles de blog plus techniques, où nous nous concentrerons sur une problématique à la fois (sinon ce blog post serait beaucoup trop long !).&lt;/p&gt;

&lt;p&gt;Comme pour toute nouvelle version, il faut s’attendre à quelques corrections mineures dans les prochains jours, lorsque de nouveaux problèmes seront détectés au fur et à mesure du déploiement sur différentes configurations.&lt;/p&gt;

&lt;p&gt;Suivez mes aventures sur Bluesky &lt;a href="https://bsky.app/profile/pykpyky.bsky.social" rel="noopener noreferrer"&gt;@pykpyky.bsky.social&lt;/a&gt; pour rester informé de toutes les nouveautés. Vous pouvez également explorer le projet complet sur &lt;a href="https://github.com/MarieLePanda/PandApache3" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; et me rejoindre pour des sessions de codage en direct sur &lt;a href="https://www.twitch.tv/pykpyky" rel="noopener noreferrer"&gt;Twitch&lt;/a&gt; pour des moments excitants et interactifs. À bientôt derrière l’écran !&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Designing a scalable application with interfaces in C#</title>
      <dc:creator>Mary 🇪🇺 🇷🇴 🇫🇷</dc:creator>
      <pubDate>Wed, 13 Nov 2024 13:51:24 +0000</pubDate>
      <link>https://dev.to/pykpyky/designing-a-scalable-application-with-interfaces-in-c-2971</link>
      <guid>https://dev.to/pykpyky/designing-a-scalable-application-with-interfaces-in-c-2971</guid>
      <description>&lt;p&gt;The PandApache3 web server is based on a modular architecture. But what does that mean in really?&lt;/p&gt;




&lt;h2&gt;
  
  
  The modules of a web server
&lt;/h2&gt;

&lt;p&gt;If we consider the PandApache3 service as a "black box," we can still break it down into three main modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web Module&lt;/strong&gt;: Handles HTTP requests on port 80, providing access to the site hosted by PandApache3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin Module&lt;/strong&gt;: Provides a web interface for administering the PandApache service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telemetry Module&lt;/strong&gt;: Used to collect and analyze data on the service's performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These modules work in synergy but can also be run independently, which is very convenient during development. For example, you can activate only the telemetry module for testing without being bothered by the other modules. This flexibility simplifies testing and speeds up development cycles.&lt;/p&gt;

&lt;p&gt;This architecture also makes future improvements much easier. For instance, if I want to add a new feature to the server, I just need to write a new module. Thanks to this approach, the new module can easily integrate into the rest of the code without disrupting existing modules.&lt;/p&gt;




&lt;h2&gt;
  
  
  Simplified integration with interfaces
&lt;/h2&gt;

&lt;p&gt;To standardize each module's behavior, PandApache3 uses a C# interface called &lt;code&gt;IModule&lt;/code&gt;. Let’s look at what an interface brings in practice.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;interface&lt;/strong&gt; in C# is a contract that defines a series of methods without implementing them. By imposing a common framework, it ensures that all modules have the same basic functionality while allowing each module to customize its behavior.&lt;/p&gt;

&lt;p&gt;Here is the &lt;code&gt;IModule&lt;/code&gt; interface used in PandApache3:&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;IModule&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;isEnable&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;Each module must be able to start, run, and stop. The &lt;code&gt;isEnable()&lt;/code&gt; method checks if the module is activated based on the service configuration. By implementing this interface, each module ensures it adheres to this structure.&lt;/p&gt;

&lt;p&gt;Example implementation for the telemetry module:&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;TelemetryModule&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IModule&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Initialize data collection&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Continuously collect telemetry metrics&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Stop data collection&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;bool&lt;/span&gt; &lt;span class="nf"&gt;isEnable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Check if the module is enabled&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ModuleInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEnable&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;The telemetry module, like the web or admin module, uses this interface to implement its own features.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using polymorphism
&lt;/h2&gt;

&lt;p&gt;Now that we’ve defined an interface and classes that implement it, how do we manage them uniformly? This is where &lt;strong&gt;polymorphism&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;Polymorphism is a programming concept that allows different classes to be used in the same way. By using the &lt;code&gt;IModule&lt;/code&gt; interface, PandApache3 can handle all modules uniformly, regardless of their actual type.&lt;/p&gt;

&lt;p&gt;In the main server class, modules are initialized and then stored in a dictionary of type &lt;code&gt;IModule&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&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="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Modules&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="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Module initialization&lt;/span&gt;
&lt;span class="n"&gt;TelemetryModule&lt;/span&gt; &lt;span class="n"&gt;telemetryModule&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;TelemetryModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;telemetryTaskScheduler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;ConnectionManagerModule&lt;/span&gt; &lt;span class="n"&gt;webModule&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;ConnectionManagerModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pipelines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"web"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;webTaskScheduler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;ConnectionManagerModule&lt;/span&gt; &lt;span class="n"&gt;adminModule&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;ConnectionManagerModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pipelines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;adminTaskScheduler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Telemetry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;telemetryModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;webModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adminModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Keep only the modules enabled in the configuration&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;moduleKey&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keys&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;moduleKey&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;isEnable&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Module &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;moduleKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; disabled"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Modules&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="n"&gt;moduleKey&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;Although the modules are created as &lt;code&gt;TelemetryModule&lt;/code&gt; or &lt;code&gt;ConnectionManagerModule&lt;/code&gt; objects, they are stored as &lt;code&gt;IModule&lt;/code&gt; objects. This way, we can manage them uniformly without needing to know their specific types.&lt;/p&gt;




&lt;h2&gt;
  
  
  Starting and running modules
&lt;/h2&gt;

&lt;p&gt;The server uses a loop to start all the activated modules, thanks to polymorphism. Here’s what it looks like:&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;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;moduleName&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;moduleName&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;StartAsync&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;Using &lt;code&gt;IModule&lt;/code&gt; allows starting, running, and stopping each module in the same loop, regardless of the module’s type. For example, to run all the modules:&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;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tasks&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;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;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;moduleName&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&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;Modules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;moduleName&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to stop 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="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;moduleName&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;moduleName&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;StopAsync&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;h2&gt;
  
  
  Adding a new module
&lt;/h2&gt;

&lt;p&gt;Thanks to this modular architecture, adding a new module to PandApache3 is simple: just write a new class that implements &lt;code&gt;IModule&lt;/code&gt;. The interface sets the framework, ensuring easy and consistent integration.&lt;/p&gt;

&lt;p&gt;Interfaces play a crucial role throughout PandApache3. For example, all middlewares follow the &lt;code&gt;IMiddleware&lt;/code&gt; interface, allowing them to run in sequence. Components such as the socket, logger, file manager, and even configuration manager also use interfaces, making the code more flexible and easier to test.&lt;/p&gt;

&lt;p&gt;With this modular approach and intelligent use of interfaces, PandApache3 becomes an extensible and maintainable web server, ready to evolve with new features without requiring significant changes to the existing codebase.&lt;/p&gt;




&lt;p&gt;I hope this article helped you better understand the concrete and essential role of interfaces in C#. If you’re interested in this language, know that the PandApache3 code is available on &lt;a href="https://github.com/MarieLePanda/PandApache3" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and live on &lt;a href="https://www.twitch.tv/pykpyky" rel="noopener noreferrer"&gt;Twitch&lt;/a&gt;. Feel free to follow the journey!&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>programming</category>
      <category>dotnet</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Concevoir une application évolutif avec des Interfaces en C#</title>
      <dc:creator>Mary 🇪🇺 🇷🇴 🇫🇷</dc:creator>
      <pubDate>Wed, 13 Nov 2024 13:43:34 +0000</pubDate>
      <link>https://dev.to/pykpyky/concevoir-une-application-evolutif-avec-des-interfaces-en-c-384f</link>
      <guid>https://dev.to/pykpyky/concevoir-une-application-evolutif-avec-des-interfaces-en-c-384f</guid>
      <description>&lt;p&gt;Le serveur web PandApache3 est basé sur une architecture modulaire. Mais qu'est-ce que cela signifie concrètement ? &lt;/p&gt;




&lt;h2&gt;
  
  
  Les modules d'un serveur web
&lt;/h2&gt;

&lt;p&gt;Si l’on considère le service PandApache3 comme une "boîte noire," on peut néanmoins le diviser en trois modules principaux : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Module Web&lt;/strong&gt; : Il permet de traiter les requêtes HTTP sur le port 80, donnant accès au site hébergé par PandApache3. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Module Admin&lt;/strong&gt; : Il offre une interface web pour administrer le service PandApache. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Module Télémetrie&lt;/strong&gt; : Il est utilisé pour collecter et analyser des données sur le fonctionnement du service. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ces modules fonctionnent en synergie, mais peuvent également être exécutés indépendamment, ce qui est très pratique lors du développement. Par exemple, on peut activer uniquement le module de télémétrie pour le tester, sans être gêné par les autres modules. Cette flexibilité simplifie les tests et accélère les cycles de développement. &lt;/p&gt;

&lt;p&gt;Adopter cette architecture facilite aussi grandement les améliorations futures. Par exemple, si je souhaite ajouter une nouvelle fonctionnalité au serveur, je n’ai qu’à écrire un nouveau module. Grâce à cette approche, le nouveau module peut facilement s’intégrer dans le reste du code sans perturber les modules existants. &lt;/p&gt;




&lt;h2&gt;
  
  
  Intégration facilitée avec les interfaces
&lt;/h2&gt;

&lt;p&gt;Pour standardiser le comportement de chaque module, PandApache3 utilise une interface C# appelée &lt;code&gt;IModule&lt;/code&gt;. Voyons ce qu’une interface apporte en pratique. &lt;/p&gt;

&lt;p&gt;Une &lt;strong&gt;interface&lt;/strong&gt; en C# est un contrat qui définit une série de méthodes sans les implémenter. En imposant un cadre commun, elle garantit que tous les modules possèdent les mêmes fonctionnalités de base, tout en permettant à chaque module de personnaliser son comportement. &lt;/p&gt;

&lt;p&gt;Voici l’interface &lt;code&gt;IModule&lt;/code&gt; utilisée dans PandApache3 :&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;IModule&lt;/span&gt; 

&lt;span class="p"&gt;{&lt;/span&gt; 

    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 

    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 

    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; 

    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;isEnable&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;Chaque module doit être capable de démarrer, fonctionner et s'arrêter. La méthode &lt;code&gt;isEnable()&lt;/code&gt; sert a vérifier, si le module est activé selon la configuration du service. En implémentant cette interface, chaque module s’assure de respecter cette structure. &lt;/p&gt;

&lt;p&gt;Exemple d'implémentation pour le module de télémétrie :&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;TelemetryModule&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IModule&lt;/span&gt; 

&lt;span class="p"&gt;{&lt;/span&gt; 

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 

    &lt;span class="p"&gt;{&lt;/span&gt; 

        &lt;span class="c1"&gt;// Initialiser la collecte de données &lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; 



    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 

    &lt;span class="p"&gt;{&lt;/span&gt; 

        &lt;span class="c1"&gt;// Collecte en continue des métriques de télémétrie &lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; 



    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;StopAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 

    &lt;span class="p"&gt;{&lt;/span&gt; 

        &lt;span class="c1"&gt;// Arrêter la collecte de données &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;bool&lt;/span&gt; &lt;span class="nf"&gt;isEnable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; 

    &lt;span class="p"&gt;{&lt;/span&gt; 

        &lt;span class="c1"&gt;// Vérifier si le module est activé &lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ModuleInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEnable&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;Le module de télémétrie, comme le module web ou admin, utilise cette interface pour implémenter ses propres fonctionnalités. &lt;/p&gt;




&lt;h2&gt;
  
  
  Utilisation du polymorphisme
&lt;/h2&gt;

&lt;p&gt;Maintenant que nous avons défini une interface et des classes qui l’implémentente, comment les gérer de manière uniforme ? C'est ici que le &lt;strong&gt;polymorphisme&lt;/strong&gt; intervient. &lt;/p&gt;

&lt;p&gt;Le polymorphisme est un concept de programmation qui permet d'utiliser différentes classes de manière identique. En utilisant l’interface &lt;code&gt;IModule&lt;/code&gt;, PandApache3 peut traiter tous les modules de manière uniforme, indépendamment de leur type réel. &lt;/p&gt;

&lt;p&gt;Dans la classe principale du serveur, les modules sont initialisés puis stockés dans un dictionnaire de type &lt;code&gt;IModule&lt;/code&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="k"&gt;public&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="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Modules&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="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt; 



&lt;span class="c1"&gt;// Initialisation des modules &lt;/span&gt;

&lt;span class="n"&gt;TelemetryModule&lt;/span&gt; &lt;span class="n"&gt;telemetryModule&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;TelemetryModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;telemetryTaskScheduler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

&lt;span class="n"&gt;ConnectionManagerModule&lt;/span&gt; &lt;span class="n"&gt;webModule&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;ConnectionManagerModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pipelines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"web"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;webTaskScheduler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

&lt;span class="n"&gt;ConnectionManagerModule&lt;/span&gt; &lt;span class="n"&gt;adminModule&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;ConnectionManagerModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Pipelines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"admin"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;adminTaskScheduler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 



&lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Telemetry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;telemetryModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

&lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;webModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

&lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModuleType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adminModule&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 



&lt;span class="c1"&gt;// Garder seulement les modules activés dans la configuration &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;moduleKey&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keys&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;moduleKey&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;isEnable&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; 

    &lt;span class="p"&gt;{&lt;/span&gt; 

        &lt;span class="n"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Module &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;moduleKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; désactivé"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

        &lt;span class="n"&gt;Modules&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="n"&gt;moduleKey&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;Les modules, bien que créés sous forme d'objets &lt;code&gt;TelemetryModule&lt;/code&gt; ou &lt;code&gt;ConnectionManagerModule&lt;/code&gt;, sont stockés sous forme de &lt;code&gt;IModule&lt;/code&gt;. Ainsi, nous pouvons les gérer de manière uniforme, sans avoir à connaître leur type spécifique. &lt;/p&gt;




&lt;h2&gt;
  
  
  Démarrage et exécution des modules
&lt;/h2&gt;

&lt;p&gt;Le serveur utilise une boucle pour démarrer tous les modules activés, grâce au polymorphisme. Voici à quoi cela ressemble :&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;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;moduleName&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

&lt;span class="p"&gt;{&lt;/span&gt; 

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;moduleName&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;StartAsync&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;L’utilisation de &lt;code&gt;IModule&lt;/code&gt; permet de démarrer, exécuter et arrêter chaque module dans une même boucle, sans se soucier du type de chaque module. Par exemple, pour exécuter tous les modules :&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;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tasks&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;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;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;moduleName&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

&lt;span class="p"&gt;{&lt;/span&gt; 

    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&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;Modules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;moduleName&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt; 

&lt;span class="p"&gt;}&lt;/span&gt; 



&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

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

&lt;/div&gt;



&lt;p&gt;Et pour les arrêter :&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;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;moduleName&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

&lt;span class="p"&gt;{&lt;/span&gt; 

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Modules&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;moduleName&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;StopAsync&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;h2&gt;
  
  
  Ajouter un nouveau module
&lt;/h2&gt;

&lt;p&gt;Grâce à cette architecture modulaire, ajouter un nouveau module à PandApache3 est simple : il suffit d’écrire une nouvelle classe qui implémente &lt;code&gt;IModule&lt;/code&gt;. L'interface impose le cadre, ce qui garantit une intégration facile et cohérente. &lt;/p&gt;

&lt;p&gt;Les interfaces jouent un rôle essentiel dans tout PandApache3. Par exemple, tous les middlewares suivent l’interface &lt;code&gt;IMiddleware&lt;/code&gt;, ce qui permet de les exécuter en chaîne. Des composants comme le socket, le logger, le gestionnaire de fichier et même le gestionnaire de configuration utilisent également des interfaces, rendant le code plus flexible et facile à tester. &lt;/p&gt;

&lt;p&gt;Avec cette approche modulaire et l'utilisation intelligente des interfaces, PandApache3 devient un serveur web extensible et maintenable, prêt à évoluer avec de nouvelles fonctionnalités sans nécessiter de changements importants dans la base de code existante. &lt;/p&gt;




&lt;p&gt;J’espère que cet article vous aura aidé à mieux comprendre le rôle concret et essentiel des interfaces en C#. Si ce langage vous intéresse, sachez que le code de PandApache3 est disponible sur &lt;a href="https://github.com/MarieLePanda/PandApache3" rel="noopener noreferrer"&gt;GitHub &lt;/a&gt;et en live sur &lt;a href="https://www.twitch.tv/pykpyky" rel="noopener noreferrer"&gt;Twitch&lt;/a&gt;. N’hésitez pas à suivre l’aventure !  &lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Networking: The Essential Asset for Finding a Job</title>
      <dc:creator>Mary 🇪🇺 🇷🇴 🇫🇷</dc:creator>
      <pubDate>Mon, 21 Oct 2024 13:28:42 +0000</pubDate>
      <link>https://dev.to/pykpyky/networking-the-essential-asset-for-finding-a-job-5048</link>
      <guid>https://dev.to/pykpyky/networking-the-essential-asset-for-finding-a-job-5048</guid>
      <description>&lt;p&gt;"Dear network..."  &lt;/p&gt;

&lt;p&gt;You've probably seen posts on social media starting with this phrase. Often on LinkedIn, sometimes on Twitter, and surely on other networks too. It’s the common opener for reaching out to people we know in the professional world, which isn’t called "Contacts" or "Friends," but "Network."  &lt;/p&gt;

&lt;p&gt;Not long ago, I happened to come across someone mocking those who use this method, pointing out that their GitHub was empty and they didn’t even have a portfolio. Embarrassing!&lt;/p&gt;

&lt;p&gt;In the world of development and tech, it’s true, developers are often advised to build a solid portfolio and maintain projects on GitHub when job hunting. These elements are often presented as pillars of professional success.  &lt;/p&gt;

&lt;p&gt;That’s what's supposed to help you land a job.  &lt;/p&gt;

&lt;p&gt;However...  &lt;/p&gt;

&lt;p&gt;Let’s not beat around the bush. I disagree.  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Myth of the Portfolio and Git Projects
&lt;/h2&gt;

&lt;p&gt;Sure, having a well-designed portfolio and visible Git projects is advantageous. They showcase your skills, achievements, and your ability to work on concrete projects.  &lt;/p&gt;

&lt;p&gt;However, these tools are not enough to land you a job, and in more than 93%* of cases, they don’t even help at all!  &lt;/p&gt;

&lt;p&gt;Job hunting is much more complex than having an up-to-date Git and often depends on factors other than technical skills (like luck).  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Power of Networking&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The reality is that job hunting rarely comes down to technical skills alone. More often than not, it’s about being seen in the right place at the right time, and this is frequently made possible by a strong network. &lt;br&gt;
Having a network means maximizing your chances of finding hidden opportunities (those that aren’t necessarily posted on job platforms).  &lt;/p&gt;

&lt;p&gt;When I was a kid, my mother was unemployed. One day, while we were at the mall with my dad (probably to buy a Nintendo 64 game), we stopped at a café. My dad, who truly embodies the guy with zero academic skills, started chatting with a man sitting next to us, just because he’s a talker. He explained that my mother was looking for a job in logistics. By chance, this man had positions available and gave my dad his number. That’s how my mom found a job.  &lt;/p&gt;

&lt;p&gt;In my own professional journey, I have countless similar examples, and if you ask around, you’ll find plenty more:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My first internship: Thanks to my school network/my IT teacher
&lt;/li&gt;
&lt;li&gt;My second internship: Through a guy I met during the first one, and two of my classmates got the same internship thanks to me.
&lt;/li&gt;
&lt;li&gt;Guess who else did an internship there years later? My little brother.
&lt;/li&gt;
&lt;li&gt;Apprenticeship: Found because my mom knew someone finishing their apprenticeship, so a spot opened up.
&lt;/li&gt;
&lt;li&gt;Fifth-year project: Validated thanks to a client... who was in my network.
&lt;/li&gt;
&lt;li&gt;First job at Microsoft: Job link posted by a classmate on Slack.
&lt;/li&gt;
&lt;li&gt;Last year, two people also joined Microsoft because they were in my network.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Network, network, network!!!&lt;br&gt;&lt;br&gt;
These examples (and I’ve got plenty more) show that networking can open doors that skills alone cannot. Meeting the right people and getting a recommendation often makes the difference between getting hired or not, getting an interview or not, and even knowing about a job opening or not.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Networking: A Valuable Investment
&lt;/h2&gt;

&lt;p&gt;Investing time and effort into developing your network is a far more valuable strategy than focusing solely on improving your portfolio and GitHub. This isn’t about bullshit or neglecting skills, but rather understanding how the job market truly works.&lt;/p&gt;

&lt;p&gt;Having a network doesn’t mean that skills don’t matter. On the contrary, it’s essential to keep developing your skills and working on personal projects. However, your network is the tool that will help you showcase those skills and get noticed by the right people.  &lt;/p&gt;

&lt;p&gt;No one’s going to say, "Yes, I saw your GitHub, and I’m really impressed by blah blah blah." Sorry, you need to put yourself out there; no one’s going to find you on a nerd site.  &lt;/p&gt;

&lt;p&gt;As for founders or startup entrepreneurs with genius disruptive methods who recruit through GitHub, you can spot them right away. If someone tells you they were impressed by your GitHub, even though it hasn’t been active for 5 years? Offers you a C++ gig because you did a Hello_world 15 years ago? That’s simply a sourcing method used, most of the time, in a very, very sloppy way.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Proving Your Skills
&lt;/h2&gt;

&lt;p&gt;Yes, technically, you can "prove" "skills" through your GitHub, but no one actually uses it for that. Why?  &lt;/p&gt;

&lt;p&gt;First of all, companies like procedures, something standard that can be applied to everyone. Not everyone has a GitHub; the majority of developers stop using it after their studies. The ones you see actively using it are the minority. And skill tests are designed for the majority (10 years in tech, countless interviews, and I’ve been asked for my GitHub once, I think).  &lt;/p&gt;

&lt;p&gt;And I’m not calling you a liar, honestly. But having a project on GitHub isn’t really a reliable proof of competence.  &lt;/p&gt;

&lt;p&gt;Young developers are often advised to work on projects and put them on GitHub, but that’s not for landing a job. After finishing any classroom, even a long one, even a five-year degree, we have tons of gaps. In short, we’re terrible! You need to keep practicing, a lot, to stand out from the crowd and refine your expertise.  &lt;/p&gt;

&lt;p&gt;If you’ve done a Master’s in Computer Science like me, it’s likely that 51%* of the courses won’t be useful post-graduation. And that’s normal, education covers all domains, but a Java developer won’t get much experience out of their Python courses. So, they need to keep digging into the Java environment, and this happens through projects.  &lt;/p&gt;

&lt;p&gt;GitHub is a site for your projects, for managing your projects, it’s not a CV, and thank goodness! (A CV is a whole different story, but that’s for another time).  &lt;/p&gt;




&lt;p&gt;In conclusion, although portfolios and Git projects are important tools for improving skills, their usefulness for landing a job is negligible. Your network is the most valuable asset. It multiplies your chances of finding opportunities and meeting the right people at the right time. Investing in and cultivating your network will pay off more than spending all your time perfecting your GitHub’s look.  &lt;/p&gt;

&lt;p&gt;Build your network, nurture your relationships, and be open to opportunities that may come through these connections. That’s how you’ll truly advance in your professional career.  &lt;/p&gt;




&lt;p&gt;*Statistic pulled from the Wet Finger Institute.&lt;/p&gt;

</description>
      <category>career</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
