<?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>Forem: FAMCHON Baptiste</title>
    <description>The latest articles on Forem by FAMCHON Baptiste (@bfamchon).</description>
    <link>https://forem.com/bfamchon</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%2F1040376%2F542df14f-553e-472f-944d-e5271c9db3c5.jpeg</url>
      <title>Forem: FAMCHON Baptiste</title>
      <link>https://forem.com/bfamchon</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bfamchon"/>
    <language>en</language>
    <item>
      <title>Let's talk: SOLID Principles 🇬🇧</title>
      <dc:creator>FAMCHON Baptiste</dc:creator>
      <pubDate>Thu, 19 Sep 2024 14:29:10 +0000</pubDate>
      <link>https://forem.com/claranet/solid-principles-3kld</link>
      <guid>https://forem.com/claranet/solid-principles-3kld</guid>
      <description>&lt;h2&gt;
  
  
  Why do we need principles ?
&lt;/h2&gt;

&lt;p&gt;Software would be a pain without clean code rules. But getting well written bricks of code is not enough: you can make a mess if those bricks are not well assembled.&lt;/p&gt;

&lt;p&gt;That's were architecture principles comes in.&lt;/p&gt;

&lt;p&gt;S.O.L.I.D. rules stands for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;S&lt;/strong&gt;RP: Single Responsibility Principle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;O&lt;/strong&gt;CP: Open Closed Principle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;L&lt;/strong&gt;SP: Liskov Substitution Principle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I&lt;/strong&gt;SP: Interface Segregation Principle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D&lt;/strong&gt;IP: Dependency Inversion Principle&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Single Responsibility Principle
&lt;/h2&gt;

&lt;p&gt;| &lt;em&gt;A module should be responsible to one, and only one, actor.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's understand this principle with an example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Employee&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;calculatePay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;reportHours&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;A product got two methods to calculate a pay and report working hours of an employee.&lt;/p&gt;

&lt;p&gt;Calculate pay is used by an Actor responsible of financial stuff, and hours reporting by another Actor, a manager for example.&lt;/p&gt;

&lt;p&gt;We broke the Single Responsibility Principle.&lt;/p&gt;

&lt;p&gt;Furthermore, imagine that these methods have some lines of code in common because we care about code reusability.&lt;/p&gt;

&lt;p&gt;A change in the common lines of code would satisfy the result of a method but can make the second fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open Closed Principle
&lt;/h2&gt;

&lt;p&gt;| &lt;em&gt;Code entities should be open to extensions and closed to modifications&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;printRole&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;elf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Player is elf !&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warrior&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Player is warrior !&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happens if you need to add another Player role ?&lt;/p&gt;

&lt;p&gt;You will have to modify the existing function and then violate the principle.&lt;/p&gt;

&lt;p&gt;To be compliant with the principle, you will need to use abstractions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PlayerRole&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="nf"&gt;printRole&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Player is &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRole&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PlayerRole&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getRole&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RoleElf&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;PlayerRole&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getRole&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;elf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RoleWarrior&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;PlayerRole&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;getRole&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warrior&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RoleWarrior&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;printRole&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, if you want to add a new Role, you will not have to modify existing code, but only implement PlayerRole.&lt;/p&gt;

&lt;h2&gt;
  
  
  Liskov Substitution Principle
&lt;/h2&gt;

&lt;p&gt;| &lt;em&gt;When extending a class, remember that you should be able to pass objects of the subclass in place of objects of the parent class without breaking the client code.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Imagine this Player class, we define a property and a method to make player flying...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;flying&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;fly&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flying&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Warrior&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fly&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Warriors can't fly, only Elves can !!!!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But we break the Liskov principle as defining a subclass of Player, Warrior, may break the code.&lt;/p&gt;

&lt;p&gt;Let's see how we can be compliant easily.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Elf&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;flying&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nf"&gt;fly&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flying&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;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;
  
  
  Interface Segregation Principle
&lt;/h2&gt;

&lt;p&gt;| &lt;em&gt;No client should be forced to depend on methods it does not use.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's start by an example which breaks ISP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CharacterActions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;throwMagicPower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlayableCharacter&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;CharacterActions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;throwMagicPower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I am a playable character&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NonPlayableCharacter&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;CharacterActions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;throwMagicPower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// not applicable to NPC...&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I am a non playable character&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This breaks ISP because &lt;code&gt;NonPlayableCharacter&lt;/code&gt; is forced to depend of method &lt;code&gt;throwMagicPower&lt;/code&gt; it doesn't use.&lt;/p&gt;

&lt;p&gt;To be compliant, we can split &lt;code&gt;CharacterActions&lt;/code&gt; into smaller interfaces and only implement when needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Speaker&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Magician&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;throwMagicPower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Damage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlayableCharacter&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Magician&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Speaker&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;throwMagicPower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;Damage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I am a playable character&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NonPlayableCharacter&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Speaker&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I am a non playable character&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dependency Inversion Principle
&lt;/h2&gt;

&lt;p&gt;| &lt;em&gt;Depend upon abstractions, not concretions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Dependency Inversion proposes that instead of depending on concrete class implementations, we should depend on abstractions.&lt;/p&gt;

&lt;p&gt;Even more important when it concerns business logic !&lt;/p&gt;

&lt;p&gt;Let's see what would be a bad code example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotifyUserUseCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;emailSender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EmailSender&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buyer&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`An order of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; have been created with your account`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emailNotificationSender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buyer&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;Here, our notify use-case is directly dependent of EmailSender.&lt;/p&gt;

&lt;p&gt;First, it would be hard to unit test our use-case because we cannot easily fake our Notifier.&lt;/p&gt;

&lt;p&gt;Secondly, we're coupled with an implementation detail: what if our product owner want to change from mail to SMS ?&lt;/p&gt;

&lt;p&gt;Here is a proper version implementing DIP&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;NotificationProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailNotificationProvider&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;NotificationProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// stuff to send email&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SMSNotificationProvider&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;NotificationProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// stuff to send SMS&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FakeNotificationProvider&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;NotificationProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NotifyUserUseCase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;notificationProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NotificationProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buyer&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`An order of &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; have been created with your account`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// business logic don't care about notification being a SMS or Mail&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notificationProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buyer&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;I'm Baptiste FAMCHON, Tech Lead specialized in frontend at Claranet.&lt;br&gt;
I write regularly on &lt;a href="https://dev.to/claranet"&gt;dev.to&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/baptiste-famchon/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; about web and software crafting topics.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://www.claranet.com/" rel="noopener noreferrer"&gt;Claranet&lt;/a&gt;, we can also help you think about IT modernization, cloud infrastructure, security and web development.&lt;/p&gt;

&lt;p&gt;Don't hesitate to contact us! 🚀&lt;/p&gt;

</description>
      <category>software</category>
      <category>beginners</category>
      <category>architecture</category>
      <category>learning</category>
    </item>
    <item>
      <title>Parlons IA : Stratégie RAG</title>
      <dc:creator>FAMCHON Baptiste</dc:creator>
      <pubDate>Thu, 25 Jul 2024 07:26:25 +0000</pubDate>
      <link>https://forem.com/claranet/parlons-ia-strategie-rag-5e9g</link>
      <guid>https://forem.com/claranet/parlons-ia-strategie-rag-5e9g</guid>
      <description>&lt;p&gt;Nous ne pouvons pas aborder le sujet de l'&lt;strong&gt;intelligence artificielle&lt;/strong&gt; générative sans parler des &lt;strong&gt;stratégies RAG&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Dans un monde où les données explosent, la &lt;strong&gt;recherche d'informations&lt;/strong&gt; pertinentes devient de plus en plus &lt;strong&gt;complexe&lt;/strong&gt;. Les technologies de l'IA ont ouvert de nouvelles perspectives pour optimiser la recherche et la génération de réponses. &lt;/p&gt;

&lt;p&gt;Parmi ces innovations, les stratégies &lt;strong&gt;RAG&lt;/strong&gt; se démarquent par leur &lt;strong&gt;efficacité&lt;/strong&gt; et leur &lt;strong&gt;précision&lt;/strong&gt; ! &lt;/p&gt;

&lt;p&gt;Partons explorer les fondements, les avantages et les applications de ces stratégies RAG.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAG: késako ?
&lt;/h2&gt;

&lt;p&gt;Retrieval Augmented Generation, pour génération augmentée de récupération, est une stratégie de &lt;strong&gt;surcharge de l'IA générative&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Au lieu de se baser uniquement sur le contenu de la base d'entraînement du modèle, qui est constituée de sources diverses comme des articles sur le web, wikipédia, des livres, des papiers scientifiques... La stratégie RAG va induire, au préalable, une &lt;strong&gt;recherche depuis une base de données externe&lt;/strong&gt; afin de &lt;strong&gt;fournir plus de contexte&lt;/strong&gt; pour la génération de réponses par le LLM.&lt;/p&gt;

&lt;p&gt;Ce qui a pour avantage de &lt;strong&gt;réduire les hallucinations&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Se pose alors les &lt;strong&gt;questions&lt;/strong&gt; sur le &lt;strong&gt;choix des données sources&lt;/strong&gt;, sur la manière dont on va &lt;strong&gt;interroger ces données&lt;/strong&gt; (étape 2), sur le &lt;strong&gt;choix des modèles&lt;/strong&gt;... &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Chaque question à son importance, creusons ces points et tentons d'y voir plus clair !&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Choix des données sources
&lt;/h2&gt;

&lt;p&gt;Le &lt;strong&gt;succès&lt;/strong&gt; d'une stratégie RAG va grandement &lt;strong&gt;dépendre des documents utilisés&lt;/strong&gt; pour récupérer les informations.&lt;br&gt;
Il faudra surtout chercher des sources :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pertinentes et liées à votre domaine d'application&lt;/strong&gt;.&lt;br&gt;
Si vous souhaitez faciliter la recherche dans vos documents d'entreprise, certains dossiers de votre Drive sont sûrement plus intéressants que d'autres.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fiable et d'actualité&lt;/strong&gt;.&lt;br&gt;
On s'en doute, on ne peut pas s'attendre à avoir des réponses cohérentes avec des sources dépassées. D'où la nécessité de cibler les bonnes sources, et les bons documents, en accordant peut être + de poids aux documents les + récents ?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Suffisamment diversifiée, mais sans diluer l'information&lt;/strong&gt;.&lt;br&gt;
Afin de convenir à vos utilisateurs, il pourrait être utile de travailler avec de la variété: ce qui intéresse les commerciaux intéresse sûrement moins les équipes produits...&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Posez-vous ces questions car toute réponse est contextuelle.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  La vectorisation comme principal allié
&lt;/h2&gt;

&lt;p&gt;Dans les stratégies RAG, la &lt;strong&gt;vectorisation&lt;/strong&gt; des sources est souvent utilisées. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mais d'abord, qu'est-ce qu'une donnée vectorisée et qu'apporte-t-elle ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On connaît très bien les données représentées sous forme de table, comme c'est le cas dans un SGBD traditionnel.&lt;/p&gt;

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

&lt;p&gt;Cependant, on imagine qu'il va être difficile d'aller rechercher les films similaires dans cette base de données, il faudrait user de requêtes complexes, basées sur plusieurs attributs.&lt;/p&gt;

&lt;p&gt;Dans ce contexte de &lt;strong&gt;recherche de proximité&lt;/strong&gt;, les bases de données &lt;strong&gt;vectorielles excellent&lt;/strong&gt;. &lt;br&gt;
Elles sont &lt;strong&gt;optimisées&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pour &lt;strong&gt;l'indexation&lt;/strong&gt;, à travers des stratégies comme Locality Sensitive Hasing (LSH), Inverted File (IVF) ou encore Hierarchical Navigable Small Worlds (HNSW)...&lt;/li&gt;
&lt;li&gt;Pour la &lt;strong&gt;récupération&lt;/strong&gt;, à travers des calculs de proximité comme Euclidean Distance, Dot Product Similarity ou encore Cosine Similarity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://vespa.ai/" rel="noopener noreferrer"&gt;Vespa&lt;/a&gt;, &lt;a href="https://www.pinecone.io/" rel="noopener noreferrer"&gt;Pinecone&lt;/a&gt;, &lt;a href="https://qdrant.tech/" rel="noopener noreferrer"&gt;Qdrant&lt;/a&gt;, &lt;a href="https://www.trychroma.com/" rel="noopener noreferrer"&gt;Chroma&lt;/a&gt;, toutes vont avoir leurs particularités et leurs spécialités.&lt;/p&gt;

&lt;p&gt;Si l'on devait imaginer nos films précédents dans une représentation vectorielle, on pourrait les voir comme ceci.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Choix des modèles de Chat &amp;amp; Embeddings
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Nous avons parlé de vecteurs précédemment, mais comment transformer nos données sous cette forme ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;C'est là qu'interviennent les modèles d'embedding !&lt;/p&gt;

&lt;p&gt;Les &lt;strong&gt;embeddings&lt;/strong&gt; vont jouer un rôle important car ils vont vous permettre de passer d'une &lt;strong&gt;représentation textuelle&lt;/strong&gt; en une représentation &lt;strong&gt;vectorielle&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Un modèle d'embedding connu, &lt;code&gt;text-embedding-ada-002&lt;/code&gt; par OpenAI par exemple.&lt;/p&gt;

&lt;p&gt;Chaque modèle va avoir ses &lt;strong&gt;spécificités&lt;/strong&gt;, liées notamment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;aux &lt;strong&gt;corpus&lt;/strong&gt;, les données utilisées pour l'entraînement&lt;/li&gt;
&lt;li&gt;et à la &lt;strong&gt;dimensionnalité&lt;/strong&gt;, le nombre de valeur numérique qui composera un vecteur&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Comment choisir un bon modèle d'embedding ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Là encore, toute réponse est contextuelle !&lt;/p&gt;

&lt;p&gt;Imaginez que tous vos &lt;strong&gt;documents sont en français&lt;/strong&gt;, et que vous utilisez un &lt;strong&gt;modèle&lt;/strong&gt; entraîné sur des données &lt;strong&gt;en anglais&lt;/strong&gt; ?&lt;br&gt;
Les résultats ne pourront pas être pertinent, les relations entre les mots vont avoir du mal à être détectées par les algorithmes.&lt;/p&gt;

&lt;p&gt;Dans ce cas, il faudrait peut-être s'orienter vers un &lt;strong&gt;modèle multilingue&lt;/strong&gt; comme &lt;a href="https://huggingface.co/intfloat/multilingual-e5-large" rel="noopener noreferrer"&gt;intfloat/multilingual-e5-large&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Concernant les modèles de Chat (pensez à &lt;code&gt;gpt-4&lt;/code&gt; par exemple), on ne va pas forcément chercher ceux les plus complets, multi-modaux, entraînés sur des corpus énormes, mais plutôt chercher un modèle &lt;strong&gt;peu coûteux et fiable&lt;/strong&gt; dans sa capacité à générer des &lt;strong&gt;phrases fluides adaptées au contexte&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Je pense notamment à un &lt;code&gt;GPT-3.5-Turbo&lt;/code&gt; ou un &lt;code&gt;Mistral Small&lt;/code&gt; qui feraient parfaitement l'affaire.&lt;/p&gt;

&lt;h2&gt;
  
  
  L'importance du Chunking
&lt;/h2&gt;

&lt;p&gt;Nous avons parlé de l'importance du choix des sources de données et de leur vectorisation à travers des modèles d'embeddings.&lt;/p&gt;

&lt;p&gt;Il nous reste à évoquer &lt;strong&gt;le chunking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Le Chunking est une technique qui sera utilisée pour &lt;strong&gt;diviser la données en morceaux&lt;/strong&gt; (= chunks) plus petits, facilitant l'indexation et la recherche.&lt;/p&gt;

&lt;p&gt;Imaginons une étude scientifique, rédigée sur plusieurs centaines de pages. Il serait &lt;strong&gt;inconcevable&lt;/strong&gt; de venir l'&lt;strong&gt;indexer entièrement en un seul gros bloc&lt;/strong&gt; !&lt;br&gt;
Dans notre recherche de proximité, si l'algorithme arrive à nous retourner cette donnée, on se retrouverait avec une &lt;strong&gt;information diluée&lt;/strong&gt; et un &lt;strong&gt;contexte énorme&lt;/strong&gt; à passer au modèle de Chat. Une démarche pas vraiment optimisée.&lt;/p&gt;

&lt;p&gt;Le chunking dans ce cas, serait le &lt;strong&gt;découpage&lt;/strong&gt; de cette étude &lt;strong&gt;en paragraphe&lt;/strong&gt;, par exemple.&lt;br&gt;
&lt;strong&gt;L'information&lt;/strong&gt; pertinente serait plus &lt;strong&gt;facilement identifiée&lt;/strong&gt; car on travaillerai avec des &lt;strong&gt;segments plus petits&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Toute la difficulté sera d'&lt;strong&gt;identifier la bonne taille&lt;/strong&gt; pour nos chunks, dépendante aussi de la donnée traitée.&lt;br&gt;
Une segmentation trop fine diminuera le contexte, une segmentation trop importante et on retombe dans l'exemple précédent.&lt;/p&gt;

&lt;p&gt;Un titre d'une étude pourrait être un bon délimiteur de chunk&lt;br&gt;
Un paragraphe dans une page Confluence aussi&lt;br&gt;
Ou encore, une ligne d'un CSV, une slide d'un diaporama...&lt;/p&gt;

&lt;h2&gt;
  
  
  Concrétisations business
&lt;/h2&gt;

&lt;p&gt;Selon moi, quelques cas d'usage sortent du lot.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;La recherche d'information en entreprise&lt;/strong&gt;.&lt;br&gt;
Plus votre entreprise grandit, plus votre connaissance augmente, et plus ça devient un calvaire pour les équipes de trouver l'information.&lt;br&gt;
Le RAG répond parfaitement à ce besoin en venant cibler les sources d'informations les + pertinentes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;L'assistance client&lt;/strong&gt;.&lt;br&gt;
Qu'on se le dise, le chatbot classique n'apporte pas une vraie satisfaction dans l'assistance qu'il apporte, souvent très limitée.&lt;br&gt;
À la place, imaginez qu'un utilisateur ait accès à une base d'assistance, une FÀQ géante qu'il pourrait interroger en language naturel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Secteur de la recherche ou médical&lt;/strong&gt;.&lt;br&gt;
Les professionnels de santé pourraient utiliser une stratégie RAG pour se tenir à jour sur les dernières études et ainsi améliorer les protocoles de soins des patients.&lt;br&gt;
Un cas d'usage applicable à d'autres professions, comme le droit ou la recherche de manière générale.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chez Claranet, nous travaillons principalement avec plusieurs solutions pour proposer la recherche documentaire: &lt;a href="https://learn.microsoft.com/fr-fr/azure/search/search-what-is-azure-search" rel="noopener noreferrer"&gt;Azure AI Search&lt;/a&gt;, &lt;a href="https://aws.amazon.com/fr/bedrock/" rel="noopener noreferrer"&gt;AWS Bedrock&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/danswer-ai/danswer" rel="noopener noreferrer"&gt;DAnswer&lt;/a&gt;, une solution open-source sur laquelle nous contribuons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ressources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ai.meta.com/blog/retrieval-augmented-generation-streamlining-the-creation-of-intelligent-natural-language-processing-models/" rel="noopener noreferrer"&gt;https://ai.meta.com/blog/retrieval-augmented-generation-streamlining-the-creation-of-intelligent-natural-language-processing-models/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/ai-assimilating-intelligence/rag-strategies-for-personal-development-learning-d3c7f0c97fd6" rel="noopener noreferrer"&gt;https://medium.com/ai-assimilating-intelligence/rag-strategies-for-personal-development-learning-d3c7f0c97fd6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/fr-fr/azure/search/search-what-is-azure-search" rel="noopener noreferrer"&gt;https://learn.microsoft.com/fr-fr/azure/search/search-what-is-azure-search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.danswer.ai/" rel="noopener noreferrer"&gt;https://www.danswer.ai/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Je suis Baptiste FAMCHON, Tech Lead spécialisé frontend chez Claranet.&lt;br&gt;
J'écris régulièrement sur dev.to et LinkedIn à propos de sujets autour du web et de l'artisanat logiciel.&lt;/p&gt;

&lt;p&gt;Chez Claranet, nous vous accompagnons aussi dans vos réflexions de modernisation SI, d'infrastructure cloud, de sécurité et de développement web.&lt;/p&gt;

&lt;p&gt;N'hésitez pas à nous contacter ! 🚀&lt;/p&gt;

</description>
      <category>rag</category>
      <category>chatgpt</category>
      <category>ai</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Parlons pattern: BFF</title>
      <dc:creator>FAMCHON Baptiste</dc:creator>
      <pubDate>Wed, 19 Jun 2024 15:23:16 +0000</pubDate>
      <link>https://forem.com/claranet/parlons-pattern-bff-34jn</link>
      <guid>https://forem.com/claranet/parlons-pattern-bff-34jn</guid>
      <description>&lt;p&gt;Aujourd'hui, nous allons parler pattern.&lt;br&gt;
Il se présente comme le meilleur ami des développeurs (BFF, you got it ? 😶), mais on l'utilise souvent sans le connaître.&lt;br&gt;
Penchons nous sur le &lt;strong&gt;Backend For Frontend&lt;/strong&gt; !&lt;/p&gt;

&lt;h2&gt;
  
  
  Pourquoi ce pattern ?
&lt;/h2&gt;

&lt;p&gt;En développant des applications web, on est souvent confronté à une volonté de &lt;strong&gt;récupération de données&lt;/strong&gt;, majoritairement &lt;strong&gt;provenant d'API&lt;/strong&gt;...&lt;br&gt;
Et en travaillant dans un &lt;strong&gt;contexte MACH&lt;/strong&gt; (Micro Services, API-first, Cloud Native, Headless), où ces données vont &lt;strong&gt;provenir de plusieurs sources&lt;/strong&gt;, c'est souvent l'application Frontend qui va se charger de leur récupération et manipulation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodqz3lwkdz98zulx6bef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodqz3lwkdz98zulx6bef.png" alt="Une application Frontend appel plusieurs API"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tant que l'&lt;strong&gt;application&lt;/strong&gt; reste &lt;strong&gt;simple&lt;/strong&gt;, on pourrait se &lt;strong&gt;contenter de ce fonctionnement&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Mais on imagine bien que plus l'&lt;strong&gt;application gagne en cas d'usage&lt;/strong&gt;, plus on va se retrouver avec beaucoup de &lt;strong&gt;logique de transformation, d’agrégation&lt;/strong&gt;...&lt;br&gt;
Et ce, dans &lt;strong&gt;chaque application Frontend du périmètre&lt;/strong&gt;: mobile, web, embarquée...&lt;/p&gt;

&lt;p&gt;Dans notre exemple précédent, on imagine de multiples API, mais on pourrait aussi imaginer un Backend unique !&lt;/p&gt;

&lt;p&gt;Et dans ce cas, la donnée exposée aux Frontend se verra de plus en plus générique, de moins en moins spécialisée aux usages des Frontend... Et on se retrouvera avec les même problématiques: transformation, aggregation etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F509duyatv3yuz37kiyuf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F509duyatv3yuz37kiyuf.png" alt="De multiples Frontend appellent un backend unique"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Est-ce vraiment ce que l'équipe souhaite ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Dans le cas où la réponse serait négative, continuons la (re)découverte de ce pattern !&lt;/p&gt;

&lt;h2&gt;
  
  
  Qu'est ce qu'un BFF ?
&lt;/h2&gt;

&lt;p&gt;Un Backend For Frontend, c'est donc une &lt;strong&gt;couche&lt;/strong&gt; qui se place &lt;strong&gt;entre le Frontend et les API&lt;/strong&gt;.&lt;br&gt;
Au lieu de laisser le Frontend &lt;strong&gt;faire les appels&lt;/strong&gt; aux différentes API et &lt;strong&gt;agréger&lt;/strong&gt;, on se contente de faire un appel au &lt;strong&gt;BFF qui va lui faire le travail&lt;/strong&gt;.&lt;br&gt;
On déplace le travail côté serveur en évitant de surcharger le navigateur avec des tâches à faible valeur ajoutée.&lt;/p&gt;

&lt;p&gt;Le BFF va finalement retourner une &lt;strong&gt;réponse prête à être consommée&lt;/strong&gt; par les Frontend du même type.&lt;br&gt;
Par exemple, il y a de fortes chances pour que la donnée sur &lt;strong&gt;iOS&lt;/strong&gt; et &lt;strong&gt;Android&lt;/strong&gt; soit utilisée de la même manière, &lt;strong&gt;un BFF pour ces deux applications&lt;/strong&gt; pourrait suffire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comment l'implémenter ?
&lt;/h2&gt;

&lt;p&gt;Partons d'un existant où tous vos Frontend appelleraient un ensemble d'API. Chaque Frontend va porter sa logique de manipulation des données.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F78qs1z6nicv8ryfbt8hd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F78qs1z6nicv8ryfbt8hd.png" alt="L'existant: de multiples frontend et une multitude d'API"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nous allons introduire une couche supplémentaire côté mobile et côté web, notre BFF.&lt;/p&gt;

&lt;p&gt;Au passage, nous profiterons ainsi de facilités d'évolution pour nos applications mobiles, car &lt;strong&gt;moins soumises au redéploiement sur les stores&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F646qtmdfws0lut8y1gwy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F646qtmdfws0lut8y1gwy.png" alt="La cible: deux BFF, un pour le mobile et un pour le web"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Quelles différences avec l'API Gateway ?
&lt;/h2&gt;

&lt;p&gt;On pourrait se poser la question car les deux solutions ont le même esprit: une &lt;strong&gt;couche passerelle&lt;/strong&gt; entre des applications consommatrices de données, et des services qui la propose.&lt;/p&gt;

&lt;p&gt;Les grandes différences se situent :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sur le périmètre adressé&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Là où l'&lt;strong&gt;API Gateway&lt;/strong&gt; va servir de &lt;strong&gt;point unique&lt;/strong&gt;, on l'a vu, le &lt;strong&gt;BFF&lt;/strong&gt; va être &lt;strong&gt;dédié pour un seul type de client&lt;/strong&gt;: mobile, web, console de jeu... Car chaque &lt;strong&gt;type de client&lt;/strong&gt; peut avoir &lt;strong&gt;sa spécificité&lt;/strong&gt; en terme de présentation de donnée, de gestion d'erreurs, d'authentification etc&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sur les responsabilités&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Techniquement, vous allez pouvoir &lt;strong&gt;gérer du cache&lt;/strong&gt;, du &lt;strong&gt;rate limiting&lt;/strong&gt;, de la &lt;strong&gt;monétisation&lt;/strong&gt; côté serveur... Mais &lt;strong&gt;ce n'est vraiment pas le but du BFF&lt;/strong&gt; !&lt;br&gt;
Il faudrait plutôt privilégier des solutions d'&lt;strong&gt;API Gateway / Management&lt;/strong&gt; comme &lt;a href="https://www.gravitee.io/" rel="noopener noreferrer"&gt;Gravitee&lt;/a&gt;.&lt;br&gt;
Et donc vous l'avez compris, on peut tout à fait combiner BFF et API Gateway, ils n'ont pas les mêmes usages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Ça pourrait mériter un article dédié !&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Comment se décider ?
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Voyons quelques avantages et inconvénients à l'usage du pattern BFF.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;+ Résilient aux changements d'API&lt;/li&gt;
&lt;li&gt;+ Gestion des erreurs provenant des API, retransmission dans un format intéressant pour les Frontend&lt;/li&gt;
&lt;li&gt;+ Une donnée présentée sur mesure&lt;/li&gt;
&lt;li&gt;- Nécessité d'avoir + de compétences fullstack dans l'équipe&lt;/li&gt;
&lt;li&gt;- Maintient d'une couche supplémentaire&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La question intéressante à se poser pour savoir si l'implémentation d'un BFF est nécessaire, serait:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Est-ce que j'ai besoin de proposer une expérience différentes aux utilisateurs selon le type d'application ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Prenons l'exemple d'une console de salon.&lt;br&gt;
Il y a de grande chance pour que l'&lt;strong&gt;expérience utilisateur&lt;/strong&gt; souhaitée soit &lt;strong&gt;différente&lt;/strong&gt; selon l'application utilisée: dashboard web, application mobile, interface de la console...&lt;br&gt;
La donnée sera présentée différemment, l'authentification sera différente...&lt;br&gt;
Le pattern BFF aurait tout à fait sa place dans ce contexte.&lt;/p&gt;

&lt;p&gt;Par contre, si vous développez une &lt;strong&gt;application unique&lt;/strong&gt;, avec un &lt;strong&gt;scope réduit&lt;/strong&gt;, la &lt;strong&gt;complexité&lt;/strong&gt; induite par une nouvelle couche dans votre architecture ne serait &lt;strong&gt;pas nécessaire&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quelques réflexions supplémentaires...
&lt;/h2&gt;

&lt;p&gt;Réfléchir au pattern BFF, c'est déjà avoir &lt;strong&gt;envisagé les micro-services&lt;/strong&gt; comme une solution potentielle.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Est-ce vraiment le cas, quels seraient les cas d'usages parfait pour ce besoin d'architecture ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Est-ce que j'ai plutôt intérêt à partir sur le développement d'un monolithe modulaire ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Comment mettre en place une API Gateway dans mon architecture ?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ressources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/fr-fr/dotnet/architecture/microservices/architect-microservice-container-applications/direct-client-to-microservice-communication-versus-the-api-gateway-pattern" rel="noopener noreferrer"&gt;The API gateway pattern versus the Direct client-to-microservice communication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=SSo-z16wEnc&amp;amp;ab_channel=GoingHeadlesswithJohn" rel="noopener noreferrer"&gt;What Is A Backend For A Frontend (BFF) Architecture Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=GCx0aouuEkU&amp;amp;ab_channel=ArpitBhayani" rel="noopener noreferrer"&gt;Backend for Frontend Pattern in Microservices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/mobilepeople/backend-for-frontend-pattern-why-you-need-to-know-it-46f94ce420b0" rel="noopener noreferrer"&gt;Backend for frontend (BFF) pattern— why do you need to know it?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wundergraph.com/blog/7-key-lessons-i-learned-while-building-bffs" rel="noopener noreferrer"&gt;7 Key Lessons I Learned While Building Backends-for-Frontends
&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://microservices.io/patterns/apigateway.html" rel="noopener noreferrer"&gt;Pattern: API Gateway / Backends for Frontends&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Je suis Baptiste FAMCHON, Tech Lead spécialisé frontend chez Claranet.&lt;br&gt;
J'écris régulièrement sur &lt;a href="https://dev.to/claranet"&gt;dev.to&lt;/a&gt; et &lt;a href="https://www.linkedin.com/in/baptiste-famchon/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; à propos de sujets autour du web et de l'artisanat logiciel.&lt;/p&gt;

&lt;p&gt;Chez &lt;a href="https://www.claranet.com/" rel="noopener noreferrer"&gt;Claranet&lt;/a&gt;, nous vous accompagnons aussi dans vos réflexions de modernisation SI, d'infrastructure cloud, de sécurité et de développement web.&lt;/p&gt;

&lt;p&gt;N'hésitez pas à nous contacter ! 🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>microservices</category>
      <category>frontend</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
