<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="pl-PL"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" hreflang="pl-PL" /><updated>2025-05-17T09:49:26+00:00</updated><id>/feed.xml</id><title type="html">Design Develop Test Deploy</title><subtitle>o wytwarzaniu oprogramowania</subtitle><author><name>mikedevbo</name></author><entry><title type="html">2022 - Co było…Co jest…Co będzie…</title><link href="/2022/01/08/what-was-what-is-what-will-be.html" rel="alternate" type="text/html" title="2022 - Co było…Co jest…Co będzie…" /><published>2022-01-08T00:00:00+00:00</published><updated>2022-01-08T00:00:00+00:00</updated><id>/2022/01/08/what-was-what-is-what-will-be</id><content type="html" xml:base="/2022/01/08/what-was-what-is-what-will-be.html"><![CDATA[<p>No i mamy styczeń! Czas podsumowań minionego roku oraz planowania działań na rok kolejny. W moim przypadku trzeci raz w blogowej formie:</p>

<ul>
  <li><a href="/2020/01/12/what-was-what-is-what-will-be.html">2020</a></li>
  <li><a href="/2021/01/06/what-was-what-is-what-will-be.html">2021</a></li>
  <li><a href="/2022/01/08/what-was-what-is-what-will-be.html">2022</a></li>
</ul>

<p>Zobaczmy, co ciekawego wydarzyło się w ostatnich 12 miesiącach.</p>

<h2 id="co-było">Co było…</h2>

<p><em>“Jeden obraz wart więcej niż tysiąc słów”</em> (chińskie przysłowie):</p>

<p><a href="/assets/2022_was_is_will_be/what_was.svg"><img src="/assets/2022_was_is_will_be/what_was.svg" alt="Picture1" /></a></p>

<p>Na początku 2021 roku postanowiłem, że nie zmieniam długofalowego celu, który polegał na zdobyciu umiejętności budowania systemów zawierających <strong>Frontend</strong>, <strong>Backend</strong> oraz <strong>Integrację z systemami zewnętrznymi</strong> z wykorzystaniem elementów wymienionych na zdjęciu. Wprowadziłem trzy-kolorowy stopień osiągnięć:</p>

<ul>
  <li>zielony
    <ul>
      <li>najwyższy, oznaczający umiejętności pozwalające na realizacje zagadnień</li>
    </ul>
  </li>
  <li>niebieski
    <ul>
      <li>praktyczne znajomość</li>
      <li>pozwala na realizację zagadnień z większym wsparciem wyszukiwania i analizowania pomocnych treści</li>
      <li>do osiągnięcia najwyższego stopnia, potrzeba więcej treningów</li>
    </ul>
  </li>
  <li>czarny
    <ul>
      <li>wiedza teoretyczna, pozwalająca dyskutować w danym temacie</li>
    </ul>
  </li>
</ul>

<p>Planowałem większą część czasu poświęcić zagadnieniom dookoła <strong>Frontendu</strong>. Kierunkiem było podejście <strong>JAMstack</strong> z wykorzystaniem frameworków <strong>C# Blazor</strong> oraz <strong>F# Bolero</strong>.</p>

<p>Osiągnięciami i przemyśleniami miałem dzielić się na tym blogu.</p>

<h2 id="co-jest">Co jest…</h2>

<p><em>“Jeden obraz wart więcej niż tysiąc słów”</em> (chińskie przysłowie):</p>

<p><a href="/assets/2022_was_is_will_be/what_is.svg"><img src="/assets/2022_was_is_will_be/what_is.svg" alt="Picture2" /></a></p>

<h3 id="design">Design</h3>

<p>Przez cały poprzedni rok nie natrafiłem na materiały, które radykalnie zmieniłby moje podejście do wytwarzania oprogramowania. Niezmiennie trzymam się koncepcji z kursu <a href="https://particular.net/adsd" title="Advanced Distributed System Design">Advanced Distributed System Design (ADSD)</a>.</p>

<p>Brakującym elementem w całej układance była wiedza z programowania <strong>Webowego</strong>. Sprawdziłem, o co w tym chodzi i od razu natrafiłem na styl architektoniczny <a href="https://guide.elm-lang.org/architecture/" title="Elm architecture">Model-View-Update (MVU)</a>. To właściwie zamknęło temat projektowania, dlatego zamieniam <strong>JAMstack</strong> na <strong>MVU</strong>, jako główny koncept projektowy.</p>

<h3 id="develop">Develop</h3>

<p>80% dostępnego czasu spędziłem na ćwiczeniu programowania <strong>Webowego</strong> z użyciem dwóch frameworków: <a href="/2021/01/23/fsharp-bolero-introduction.html">F# Bolero</a> oraz <a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor" title="C# Blazor">C# Blazor</a>. Pierwszy z nich rozszerza drugi o implementację wzorca <strong>Model-View-Update</strong> oraz wiele innych przydatnych mechanizmów.</p>

<p>Pozostałe 20% poświęciłem <a href="/2021/11/14/adsd-nservicebus-csharp-fsharp-reloaded.html">przepisywaniu</a> na język <strong>F#</strong> <a href="https://github.com/mikedevbo/blog-comments" title="Blog Comments">systemu komentarzy na blogu</a>, który zrealizowany jest z wykorzystaniem wybranych technik wymienionych na zdjęciu.</p>

<h3 id="test">Test</h3>

<p>Dalej bez zmian. Dzielenie kodu na małe, niezależne części, które można przetestować za pomocą automatycznych testów jednostkowych oraz manualnych testów integracyjnych sprawdza się dość dobrze.</p>

<h3 id="deploy">Deploy</h3>

<p>Również bez zmian. Tematy czekają na swoją kolej.</p>

<h3 id="blog">Blog</h3>

<p>Od strony wizualnej nic się nie zmieniło. Napisałem 8 artykułów, o dwa mniej w porównaniu do poprzedniego roku.</p>

<h2 id="co-będzie">Co będzie…</h2>

<p>Rok temu, o tej porze, dokładnie wiedziałem, czym będę się zajmował w kolejnych miesiącach. Dzisiaj nie mam sprecyzowanych celów. Plan jest taki, aby na bieżąco wybierać tematy z puli, zgłębiać zagadnienie i dzielić się poznaną wiedzą.</p>

<p>To tyle, jeśli chodzi o podsumowanie minionego roku. Dzięki za przeczytanie artykułu. Powodzenia z Twoimi planami oraz ich realizacją!</p>

<p>=</p>

<h3> </h3>]]></content><author><name>mikedevbo</name></author><summary type="html"><![CDATA[Podsumowanie 2021 roku. Plany na rok 2022.]]></summary></entry><entry><title type="html">ADSD - NServiceBus - C# - F# - Reloaded</title><link href="/2021/11/14/adsd-nservicebus-csharp-fsharp-reloaded.html" rel="alternate" type="text/html" title="ADSD - NServiceBus - C# - F# - Reloaded" /><published>2021-11-14T00:00:00+00:00</published><updated>2021-11-14T00:00:00+00:00</updated><id>/2021/11/14/adsd-nservicebus-csharp-fsharp-reloaded</id><content type="html" xml:base="/2021/11/14/adsd-nservicebus-csharp-fsharp-reloaded.html"><![CDATA[<p>No i stało się! <strong>System komentarzy</strong>, na którym ćwiczę <a href="/2021/01/06/what-was-what-is-what-will-be.html">różne koncepcje</a> związane z wytwarzaniem oprogramowania, doczekał się <a href="https://github.com/mikedevbo/blog-comments/releases" title="GitHub Releases">nowej wersji</a>. Do tej pory bazował na <a href="/2020/04/13/adsd-nservicebus-csharp-fsharp-perfect-quartet.html">wielkiej czwórce</a>. Najnowsza wersja zrealizowana jest z wykorzystaniem trójki z nich:</p>

<ul>
  <li><a href="https://particular.net/adsd" title="ADSD course">ADSD</a></li>
  <li><a href="https://particular.net/nservicebus" title="NServiceBus framework">NServiceBus</a></li>
  <li><a href="https://fsharp.org" title="Fsharp language">F#</a></li>
</ul>

<p>Dlaczego tak? Cała historia zaczyna się <a href="/2019/09/01/fsharp-nservicebus-practical-guide-introduction.html">w tym miejscu</a> i przewija się przez wszystkie późniejsze artykuły, aczkolwiek początki sięgają <a href="/2016/11/26/wytwarzanie_oprogramowania_I.html">wpisu nr jeden</a>.</p>

<h2 id="host">Host</h2>

<p>Proces realizacji całego przedsięwzięcia składał się z trzech części:</p>

<ul>
  <li><strong>ADSD</strong> - utrzymanie <a href="/2020/09/29/adsd-nservicebus-csharp-fsharp-lets-dance-design.html">dotychczasowej</a> architektury rozwiązania</li>
  <li><strong>F#</strong> - zamiana kodu <strong>C#</strong> na <strong>F# 5</strong>, który <a href="/2020/11/08/fsharp-5-nservicebus-saga-perfect-match.html">upraszcza</a> implementację <strong>Sagi</strong></li>
  <li><strong>NServiceBus</strong> - wykorzystanie nowości wprowadzonych w <a href="https://particular.net/blog/whats-new-nservicebus-7-4" title="NServiceBus 7.4">wersji 7.4</a></li>
</ul>

<p>Zacząłem od podniesienia <strong>F#</strong> do wersji 5, <strong>NServiceBus’a</strong> do wersji 7.5.0 oraz pozostałych paczek Nuget do ich najnowszych wersji. Wszystko ładnie kompilowało się i działało.</p>

<p>Kolejnym krokiem było przerobienie projektów typu <strong>Host</strong>. Najpierw dla <strong>Web</strong>, a potem dla <strong>NServiceBus Endpoint</strong>. Po zmianie plików z <strong><em>.csproj</em></strong> na <strong><em>.fsproj</em></strong> projekty przestały się kompilować. Zamiana <strong>C#</strong> na <strong>F#</strong> była pierwszym testem w stylu:</p>

<p><em>“Czy faktycznie znam już F# na tyle dobrze, żeby móc zrealizować tego typu zadanie?”</em></p>

<p>Proces przepisywania kodu dostarczył mi niezłego fun’u :) Do realizacji używałem dwóch narzędzi:</p>

<ul>
  <li>edytora tekstu bez żadnych dodatków - <a href="https://code.visualstudio.com" title="Visual Studio Code">Visual Studio Code</a></li>
  <li><a href="https://docs.microsoft.com/en-us/dotnet/fsharp" title="Fsharp documentation">kompilatora</a> języka <strong>F#</strong></li>
</ul>

<p>Po paru przebiegach coraz lepiej rozumiałem mowę kompilatora. Szybszy efekt:</p>

<p><em>“…aha…no tak…”</em></p>

<p>Przykłady wprowadzanych zmian:</p>

<ul>
  <li>inne słowa kluczowe opisujące konstrukcje językowe np. klasy</li>
  <li>ważna kolejność ułożenia plików w projekcie - z góry na dół - elementy re-używalne muszą być “wyżej” niż elementy, które z nich korzystają</li>
  <li>zasięg określany przez wcięcia, a nie przez nawiasy {}</li>
  <li>średniki nie są potrzebne</li>
  <li>…</li>
</ul>

<h2 id="policy">Policy</h2>

<p>W tym momencie przyszła kolej na zmianę kluczowych elementów systemu - <strong>Policies</strong>, w większości zaimplementowanych, jako <strong>NServiceBus Saga</strong>. Realizując tę część, doświadczyłem tego, co naprawdę kryje się pod stwierdzeniem:</p>

<p><em>“To zależy”</em></p>

<p>W poprzedniej wersji systemu potrzebne były dwa projekty. Jeden na kod <strong>NServiceBus’a</strong> napisany w <strong>C#</strong>, drugi na kod <strong>Logiki</strong> napisanej w <strong>F#</strong>. Logicznie <strong>Saga</strong> oraz <strong>Logika</strong> stanowiły jedność. Fizycznie, były rozproszone po różnych projektach. Wszystko zgodnie z zasadami <strong>ADSD</strong>. W momencie, kiedy <strong>Saga</strong> przechodziła na <strong>F#</strong>, można było przenieść kod logiki w to samo miejsce, gdzie kod Sagi i odseparować te dwa elementy modułem. Dzięki temu zniknęła konieczność używania interoperability więc interfejs oraz klasa realizująca logikę przestały być potrzebne. Logiką stały się zwykłe <strong>pure functions</strong>, których nie trzeba nigdzie wstrzykiwać. Można ich używać tam, gdzie są potrzebne. Struktura kodu uprościła się, a zasady <strong>ADSD</strong> zostały zachowane.</p>

<p>W tym kontekście, odpowiedź na pytanie <em>“Czy potrzebny jest osobny projekt na logikę?”</em>, brzmiała <em>“To zależy”</em>. Od czego? Od tego, czy mieszany jest kod <strong>C#</strong> z kodem <strong>F#</strong>. Jeśli nie, to osobny projekt jest zbędny.</p>

<h3 id="saga-mapping">Saga Mapping</h3>

<p>W trakcie zmiany <strong>C#</strong> na <strong>F#</strong>, skorzystałem z jednej z nowości wprowadzonych w <strong>NServiceBus 7.4</strong> - <a href="https://particular.net/blog/whats-new-nservicebus-7-4#finding-sagas" title="NServiceBus Finding Saga">sposobie mapowania Sagi z Message’ami</a>.</p>

<p>Pierwotna metoda mapowania wygląda tak (przykład w <strong>C#</strong>):</p>

<figure class="highlight"><pre><code class="language-csharp" data-lang="csharp"><span class="k">protected</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">ConfigureHowToFindSaga</span><span class="p">(</span><span class="n">SagaPropertyMapper</span><span class="p">&lt;</span><span class="n">PolicyData</span><span class="p">&gt;</span> <span class="n">mapper</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">mapper</span><span class="p">.</span><span class="n">ConfigureMapping</span><span class="p">&lt;</span><span class="n">RegisterComment</span><span class="p">&gt;(</span><span class="n">message</span> <span class="p">=&gt;</span> <span class="n">message</span><span class="p">.</span><span class="n">CommentId</span><span class="p">)</span>
          <span class="p">.</span><span class="nf">ToSaga</span><span class="p">(</span><span class="n">data</span> <span class="p">=&gt;</span> <span class="n">data</span><span class="p">.</span><span class="n">CommentId</span><span class="p">);</span>

    <span class="n">mapper</span><span class="p">.</span><span class="n">ConfigureMapping</span><span class="p">&lt;</span><span class="n">ResponseCreateGitHubPullRequest</span><span class="p">&gt;(</span><span class="n">message</span> <span class="p">=&gt;</span> <span class="n">message</span><span class="p">.</span><span class="n">CommentId</span><span class="p">)</span>
          <span class="p">.</span><span class="nf">ToSaga</span><span class="p">(</span><span class="n">data</span> <span class="p">=&gt;</span> <span class="n">data</span><span class="p">.</span><span class="n">CommentId</span><span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>Najpierw podaje się Message do mapowania - <code class="language-plaintext highlighter-rouge">ConfigureMapping</code>, a potem property Sagi - <code class="language-plaintext highlighter-rouge">ToSaga</code>.</p>

<p>Nowa metoda pozwala to zrobić tak (przykład w <strong>F#</strong>):</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">ConfigureHowToFindSaga</span><span class="p">(</span><span class="n">mapper</span><span class="p">:</span> <span class="nc">SagaPropertyMapper</span><span class="p">&lt;</span><span class="nc">PolicyData</span><span class="o">&gt;)</span> <span class="p">=</span>
    <span class="n">mapper</span><span class="p">.</span><span class="nc">MapSaga</span><span class="p">(</span><span class="k">fun</span> <span class="n">saga</span> <span class="p">-&gt;</span> <span class="n">saga</span><span class="p">.</span><span class="nc">CommentId</span> <span class="p">:&gt;</span> <span class="n">obj</span><span class="p">)</span>
          <span class="p">.</span><span class="nc">ToMessage</span><span class="p">&lt;</span><span class="nc">RegisterComment</span><span class="o">&gt;(</span><span class="k">fun</span> <span class="n">message</span> <span class="p">-&gt;</span> <span class="n">message</span><span class="p">.</span><span class="nc">CommentId</span> <span class="p">:&gt;</span> <span class="n">obj</span><span class="p">)</span>
          <span class="p">.</span><span class="nc">ToMessage</span><span class="p">&lt;</span><span class="nc">ResponseCreateGitHubPullRequest</span><span class="o">&gt;(</span><span class="k">fun</span> <span class="n">message</span> <span class="p">-&gt;</span> <span class="n">message</span><span class="p">.</span><span class="nc">CommentId</span> <span class="p">:&gt;</span> <span class="n">obj</span><span class="p">)</span> <span class="p">|&gt;</span> <span class="n">ignore</span></code></pre></figure>

<p>Najpierw podaje się property Sagi - <code class="language-plaintext highlighter-rouge">MapSaga</code>, a potem wymienia się Message’e do zmapowania - <code class="language-plaintext highlighter-rouge">ToMessage</code>.</p>

<p>Niewielka z pozoru zmiana, mocno upraszcza analizę kodu.</p>

<h3 id="sql-persistence">SQL Persistence</h3>

<p>W całym procesie zmiany najbardziej obawiałem się wystąpienia niestandardowej sytuacji, która zablokuje zrealizowanie całego przedsięwzięcia. Taka sytuacja zdarzyła się, gdy testowałem zmiany z użyciem <a href="https://docs.particular.net/persistence/sql/" title="SQL Persistence">SQL Persistence</a>. W trakcie kompilacji pojawiał się komunikat:</p>

<p><code class="language-plaintext highlighter-rouge">error : SqlPersistenceTask: Unable to determine Saga correlation property because an unexpected method call was detected in the ConfigureHowToFindSaga method.</code></p>

<p>SQL Persistence próbuje wywnioskować, które property z <strong>Saga Data</strong> pełni rolę correlation.</p>

<p>Ta sama struktura kodu działała w <strong>C#</strong>, ale nie w <strong>F#</strong>. Przeglądając <a href="https://github.com/Particular/NServiceBus.Persistence.Sql" title="SQL Persistence Source Code">source code</a> oraz dokumentację okazało się, że wnioskowanie polega na przeglądaniu kodu <strong>IL</strong>, wygenerowanego z metody <code class="language-plaintext highlighter-rouge">ConfigureHowToFindSaga</code>. <strong>F#</strong> generuje dodatkowe wpisy w <strong>IL</strong> stąd przy próbie kompilacji pojawiał się powyższy komunikat</p>

<p>Wielokrotnie już pisałem o sile oraz możliwościach <strong>NServiceBus’a</strong> i tak też było w tym przypadku. Szybki wgląd w dokumentację i okazało się, że można zmienić domyślnie zachowanie, wskazując jawnie, który element ma pełnić rolę koordynatora. Służy do tego atrybut <code class="language-plaintext highlighter-rouge">[SqlSaga]</code>. Użycie w <strong>F#</strong> wygląda tak:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="p">[&lt;</span><span class="nc">SqlSaga</span><span class="p">(</span><span class="n">nameof</span> <span class="nn">Unchecked</span><span class="p">.</span><span class="n">defaultof</span><span class="p">&lt;</span><span class="nc">PolicyData</span><span class="o">&gt;.</span><span class="nc">CommentId</span><span class="o">)&gt;]</span>
<span class="k">type</span> <span class="nc">CommentRegistrationPolicy</span><span class="bp">()</span> <span class="p">=</span>
    <span class="o">...</span></code></pre></figure>

<p>Dokumentacja wskazuje o stosowaniu tej konstrukcji w sytuacjach niestandardowych. Połączenie <strong>NServiceBus’a</strong> z <strong>F#</strong> można uznać za taką sytuację ;)</p>

<h2 id="endpoint-common">Endpoint Common</h2>

<p>Następnym elementem do zmiany był <strong>Endpoint Common</strong>, którego intencją jest współdzielenie podstawowej konfiguracji, pomiędzy wszystkimi Endpoint’ami.</p>

<h3 id="multiple-message-conventions">Multiple Message Conventions</h3>

<p>Ponownie, w trakcie zmiany <strong>C#</strong> na <strong>F#</strong>, skorzystałem z jednej z nowości <strong>NServiceBus’a</strong> - <a href="https://particular.net/blog/whats-new-nservicebus-7-4#multiple-message-conventions" title="NServiceBus Multiple Message Convention">Multiple Message Conventions</a>.</p>

<p>Po przepisaniu kodu jeden do jeden, konfiguracja konwencji wyglądała tak:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">conventions</span> <span class="p">=</span> <span class="n">endpointConfiguration</span><span class="p">.</span><span class="nc">Conventions</span><span class="bp">()</span>
<span class="n">conventions</span><span class="p">.</span><span class="nc">DefiningCommandsAs</span><span class="p">(</span><span class="k">fun</span> <span class="n">t</span> <span class="p">-&gt;</span> <span class="n">t</span><span class="p">.</span><span class="nc">Namespace</span> <span class="p">&lt;&gt;</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">t</span><span class="p">.</span><span class="nn">Namespace</span><span class="p">.</span><span class="nc">EndsWith</span><span class="p">(</span><span class="s2">"Commands"</span><span class="p">)</span> <span class="o">||</span> <span class="n">t</span><span class="p">.</span><span class="nc">IsAssignableFrom</span><span class="p">(</span><span class="n">typeof</span><span class="p">&lt;</span><span class="nc">ICommand</span><span class="o">&gt;))</span> <span class="p">|&gt;</span> <span class="n">ignore</span>
<span class="n">conventions</span><span class="p">.</span><span class="nc">DefiningEventsAs</span><span class="p">(</span><span class="k">fun</span> <span class="n">t</span> <span class="p">-&gt;</span> <span class="n">t</span><span class="p">.</span><span class="nc">Namespace</span> <span class="p">&lt;&gt;</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">t</span><span class="p">.</span><span class="nn">Namespace</span><span class="p">.</span><span class="nc">EndsWith</span><span class="p">(</span><span class="s2">"Events"</span><span class="p">)</span> <span class="o">||</span> <span class="n">t</span><span class="p">.</span><span class="nc">IsAssignableFrom</span><span class="p">(</span><span class="n">typeof</span><span class="p">&lt;</span><span class="nc">IEvent</span><span class="o">&gt;))</span> <span class="p">|&gt;</span> <span class="n">ignore</span>
<span class="n">conventions</span><span class="p">.</span><span class="nc">DefiningMessagesAs</span><span class="p">(</span><span class="k">fun</span> <span class="n">t</span> <span class="p">-&gt;</span> <span class="n">t</span><span class="p">.</span><span class="nc">Namespace</span> <span class="p">&lt;&gt;</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">t</span><span class="p">.</span><span class="nn">Namespace</span><span class="p">.</span><span class="nc">EndsWith</span><span class="p">(</span><span class="s2">"Messages"</span><span class="p">)</span> <span class="o">||</span> <span class="n">t</span><span class="p">.</span><span class="nc">IsAssignableFrom</span><span class="p">(</span><span class="n">typeof</span><span class="p">&lt;</span><span class="nc">IMessage</span><span class="o">&gt;)</span> <span class="o">||</span> <span class="n">t</span> <span class="p">=</span> <span class="n">typeof</span><span class="p">&lt;</span><span class="nn">NServiceBus</span><span class="p">.</span><span class="nn">Mailer</span><span class="p">.</span><span class="nc">MailMessage</span><span class="o">&gt;)</span> <span class="p">|&gt;</span> <span class="n">ignore</span></code></pre></figure>

<p>Ze względu na to, że klasa <code class="language-plaintext highlighter-rouge">NServiceBus.Mailer.MailMessage</code> korzysta z domyślnej konwencji <strong>NServiceBusa</strong>, trzeba było ją zachować, używając dodatkowego warunku <code class="language-plaintext highlighter-rouge">||</code>.</p>

<p>Nowy sposób pozwala dodawać własne konwencje do już istniejących:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">conventions</span> <span class="p">=</span> <span class="n">endpointConfiguration</span><span class="p">.</span><span class="nc">Conventions</span><span class="bp">()</span>
<span class="n">conventions</span><span class="p">.</span><span class="nc">Add</span><span class="p">(</span>
    <span class="p">{</span> <span class="k">new</span> <span class="nc">IMessageConvention</span> <span class="k">with</span>
        <span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">Name</span> <span class="p">=</span> <span class="s2">"Type name suffix"</span>
        <span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">IsCommandType</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="p">=</span> <span class="n">t</span><span class="p">.</span><span class="nc">Namespace</span> <span class="p">&lt;&gt;</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">t</span><span class="p">.</span><span class="nn">Namespace</span><span class="p">.</span><span class="nc">EndsWith</span><span class="p">(</span><span class="s2">"Commands"</span><span class="p">)</span>
        <span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">IsEventType</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="p">=</span> <span class="n">t</span><span class="p">.</span><span class="nc">Namespace</span> <span class="p">&lt;&gt;</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">t</span><span class="p">.</span><span class="nn">Namespace</span><span class="p">.</span><span class="nc">EndsWith</span><span class="p">(</span><span class="s2">"Events"</span><span class="p">)</span>
        <span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">IsMessageType</span><span class="p">(</span><span class="n">t</span><span class="p">)</span> <span class="p">=</span> <span class="n">t</span><span class="p">.</span><span class="nc">Namespace</span> <span class="p">&lt;&gt;</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">t</span><span class="p">.</span><span class="nn">Namespace</span><span class="p">.</span><span class="nc">EndsWith</span><span class="p">(</span><span class="s2">"Messages"</span><span class="o">)})</span> <span class="p">|&gt;</span> <span class="n">ignore</span></code></pre></figure>

<p>Metoda <code class="language-plaintext highlighter-rouge">convention.Add</code> przyjmuje w parametrze implementację interfejsu <code class="language-plaintext highlighter-rouge">IMessageConvention</code>.</p>

<p>Dzięki temu zniknęła konieczność jawnego definiowania konwencji dla <code class="language-plaintext highlighter-rouge">NServiceBus.Mailer.MailMessage</code> - uproszczenie.</p>

<h3 id="object-expressions">Object Expressions</h3>

<p><strong>F#</strong> posiada bardzo fajną konstrukcję - <a href="https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/object-expressions" title="F# Object Expressions">Object Expressions</a>. Dzięki niej można zakodować implementację interfejsu w miejscu jego użycia. Przydatne w sytuacjach, w których interfejs wykorzystywany jest tylko w jednym fragmencie kodu. Konfigurowanie konwencji dla <strong>Messge’y</strong> idealnie wpasowuje się w ten scenariusz, dlatego skorzystałem z tej konstrukcji przy ich definiowaniu.</p>

<h2 id="testy">Testy</h2>

<p>Ostatnim, dużym fragmentem do dostosowania były testy jednostkowe oraz integracyjne. W wersji <strong>C#</strong> wszystkie zależności były dostarczane przez interfejsy, dzięki czemu w testach można było wstrzykiwać fake’owe implementacje. <strong>F#</strong> obsługuje interfejsy i mogłem pójść tą samą ścieżką, ale chciałem sprawdzić, czy można osiągnąć podobny efekt, korzystając z konstrukcji funkcyjnych :)</p>

<ul>
  <li>wartości dostarczane przez jeden z dostępnych typów danych: simple, tuple, record, …</li>
  <li>zachowania dostarczane przez funkcje</li>
</ul>

<p>Efekt? Policy to klasa. Jak klasa, to można w niej definiować konstruktory. <strong>NServiceBus</strong>, przy tworzeniu instancji Sagi wywołuje konstruktor bezparametrowy. Test może wołać dowolny konstruktor. Rozwiązaniem jest użycie dwóch konstruktów.</p>

<p>Przykład dla wartości:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">CommentAnswerNotificationPolicy</span><span class="p">(</span><span class="n">smtpFrom</span><span class="p">:</span> <span class="kt">string</span><span class="p">,</span> <span class="n">blogDomainName</span><span class="p">:</span> <span class="kt">string</span><span class="p">)</span> <span class="p">=</span>
    <span class="o">...</span>
    <span class="k">new</span><span class="bp">()</span> <span class="p">=</span> <span class="nc">CommentAnswerNotificationPolicy</span><span class="p">(</span>
                <span class="nn">ConfigurationProvider</span><span class="p">.</span><span class="n">smtpFrom</span><span class="p">,</span>
                <span class="nn">ConfigurationProvider</span><span class="p">.</span><span class="n">blogDomainName</span><span class="p">)</span></code></pre></figure>

<p>Główny konstruktor przyjmuje dwa parametry typu <code class="language-plaintext highlighter-rouge">string</code>. Drugi konstruktor woła pierwszy, dostarczając wartości pobrane z konfiguracji.</p>

<p>Testy:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">module</span> <span class="nc">PolicyTests</span> <span class="p">=</span>
    <span class="o">...</span>
    <span class="k">let</span> <span class="n">getPolicy</span> <span class="n">smtpFrom</span> <span class="n">blogDomainName</span> <span class="n">data</span> <span class="p">=</span>
            <span class="nc">CommentAnswerNotificationPolicy</span><span class="p">(</span><span class="n">smtpFrom</span><span class="p">,</span> <span class="n">blogDomainName</span><span class="p">,</span> <span class="nc">Data</span><span class="p">=</span><span class="n">data</span><span class="p">)</span>
    <span class="o">...</span>
    <span class="p">[&lt;</span><span class="nc">Test</span><span class="p">&gt;]</span>
    <span class="k">let</span> <span class="nc">Handle_RegisterCommentNotification_FillProperPolicyData</span> <span class="bp">()</span> <span class="p">=</span>

        <span class="c1">// Arrange</span>
        <span class="o">...</span>
        <span class="k">let</span> <span class="n">policyData</span> <span class="p">=</span> <span class="nc">PolicyData</span><span class="bp">()</span>
        <span class="k">let</span> <span class="n">policy</span> <span class="p">=</span> <span class="n">getPolicy</span> <span class="s2">"test@test.com"</span> <span class="s2">"sampleBlogDomainName"</span> <span class="n">policyData</span>
        <span class="o">...</span></code></pre></figure>

<p>Funkcja <code class="language-plaintext highlighter-rouge">getPolicy</code> przyjmuje parametry <code class="language-plaintext highlighter-rouge">smtpFrom</code>, <code class="language-plaintext highlighter-rouge">blogDomainName</code> oraz <code class="language-plaintext highlighter-rouge">data</code>. Przekazuje je do konstruktora <code class="language-plaintext highlighter-rouge">CommentAnswerNotificationPolicy</code>. Parametr <code class="language-plaintext highlighter-rouge">data</code> podstawiany jest pod publiczne property <code class="language-plaintext highlighter-rouge">Data</code>. Test woła <code class="language-plaintext highlighter-rouge">getPolicy</code>, podstawiając odpowiednie wartości.</p>

<p>Przykład dla zachowania:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">GitHubPullRequestVerificationPolicy</span> <span class="p">(</span><span class="n">checkPullRequestStatus</span><span class="p">:</span> <span class="kt">string</span> <span class="p">-&gt;</span> <span class="kt">string</span> <span class="p">-&gt;</span> <span class="nc">Async</span><span class="p">&lt;</span><span class="nc">ResponseCheckPullRequestStatus</span><span class="o">&gt;)</span> <span class="p">=</span>
    <span class="k">new</span><span class="bp">()</span> <span class="p">=</span> <span class="nc">GitHubPullRequestVerificationPolicy</span><span class="p">(</span><span class="nn">GitHub</span><span class="p">.</span><span class="n">checkPullRequestStatus</span><span class="p">)</span></code></pre></figure>

<p>Główny konstruktor przyjmuje funkcję, która zawiera dwa parametry typu <code class="language-plaintext highlighter-rouge">string</code> i zwraca, w sposób asynchroniczny, wartość <code class="language-plaintext highlighter-rouge">ResponseCheckPullRequestStatus</code>. Drugi konstruktor woła pierwszy, dostarczając implementację funkcji zgodną z oczekiwaną sygnaturą.</p>

<p>Testy:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">module</span> <span class="nc">PolicyTests</span> <span class="p">=</span>
    <span class="o">...</span>
    <span class="k">let</span> <span class="n">getPolicy</span><span class="bp">()</span> <span class="p">=</span> <span class="nc">GitHubPullRequestVerificationPolicy</span><span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">async</span> <span class="p">{</span> <span class="k">return</span> <span class="nc">ResponseCheckPullRequestStatus</span><span class="p">(</span><span class="nn">PullRequestStatus</span><span class="p">.</span><span class="nc">Open</span><span class="p">,</span> <span class="s2">"ETag_123"</span><span class="p">)</span> <span class="o">})</span>
    <span class="o">...</span>
    <span class="p">[&lt;</span><span class="nc">Test</span><span class="p">&gt;]</span>
    <span class="k">let</span> <span class="nc">Handle_RequestCheckPullRequestStatus_ProperResult</span> <span class="bp">()</span> <span class="p">=</span>

        <span class="c1">// Arrange</span>
        <span class="o">...</span>
        <span class="k">let</span> <span class="n">policy</span> <span class="p">=</span> <span class="n">getPolicy</span> <span class="bp">()</span> <span class="p">:&gt;</span> <span class="nc">IHandleMessages</span><span class="p">&lt;</span><span class="nc">RequestCheckPullRequestStatus</span><span class="p">&gt;</span>
        <span class="o">...</span></code></pre></figure>

<p>Funkcja <code class="language-plaintext highlighter-rouge">getPolicy</code> zwraca obiekt <code class="language-plaintext highlighter-rouge">GitHubPullRequestVerificationPolicy</code>. W parametrze konstruktora przekazuje funkcję lambda, symulującą zachowanie, które polega na zwróceniu statusu odpowiedzi na komentarz. Test woła <code class="language-plaintext highlighter-rouge">getPolicy</code> i rzutuje zwrócony wynik na odpowiedni typ.</p>

<p>Pozostałe zmiany dotyczyły usprawnień pracy z solucją oraz jej czyszczenie z nieużywanego kodu. Całą implementację wraz z historią zmian możesz podejrzeć na <a href="https://github.com/mikedevbo/blog-comments" title="Blog Comment Source Code">GitHub’e</a>.</p>

<h2 id="podsumowanie">Podsumowanie</h2>

<p><strong>F# 5</strong> pozwala programować <strong>NServiceBus Saga</strong>, w taki sam sposób, jak <strong>C#</strong>. <strong>NServiceBus 7.4</strong> wprowadza usprawnienia, które ułatwiają realizację końcowego produktu. Z wielkiej czwórki <a href="https://particular.net/adsd" title="ADSD course">ADSD</a>, <a href="https://particular.net/nservicebus" title="NServiceBus framework">NServiceBus</a>, <a href="https://docs.microsoft.com/en-us/dotnet/csharp/" title="C# language">C#</a>, <a href="https://fsharp.org" title="Fsharp language">F#</a> zrobiły się dwie, równoważne wielkie trójki:</p>

<ul>
  <li><strong>ADSD</strong>, <strong>NServiceBus</strong>, <strong>C#</strong></li>
  <li><strong>ADSD</strong>, <strong>NServiceBus</strong>, <strong>F#</strong></li>
</ul>

<p>Od tego momentu, jeśli dodajesz komentarz na tym blogu, to całość realizowana jest z wykorzystaniem ostatniej z nich :)</p>

<p>=</p>

<h3> </h3>]]></content><author><name>mikedevbo</name></author><summary type="html"><![CDATA[No i stało się! System komentarzy, na którym ćwiczę różne koncepcje związane z wytwarzaniem oprogramowania, doczekał się nowej wersji. Do tej pory bazował na wielkiej czwórce. Najnowsza wersja zrealizowana jest z wykorzystaniem trójki z nich.]]></summary></entry><entry><title type="html">F# Bolero - A cóż to takiego? - remoting</title><link href="/2021/08/18/fsharp-bolero-remoting.html" rel="alternate" type="text/html" title="F# Bolero - A cóż to takiego? - remoting" /><published>2021-08-18T00:00:00+00:00</published><updated>2021-08-18T00:00:00+00:00</updated><id>/2021/08/18/fsharp-bolero-remoting</id><content type="html" xml:base="/2021/08/18/fsharp-bolero-remoting.html"><![CDATA[<p>Posty z tej serii:</p>

<ul>
  <li><a href="/2021/01/23/fsharp-bolero-introduction.html">F# Bolero - A cóż to takiego? - wprowadzenie</a></li>
  <li><a href="/2021/02/07/fsharp-bolero-html-templates.html">F# Bolero - A cóż to takiego? - HTML templates</a></li>
  <li><a href="/2021/02/28/fsharp-bolero-view-components.html">F# Bolero - A cóż to takiego? - view components</a></li>
  <li><a href="/2021/04/11/fsharp-bolero-routing.html">F# Bolero - A cóż to takiego? - routing</a></li>
  <li><a href="/2021/06/12/fsharp-bolero-routing-page-models.html">F# Bolero - A cóż to takiego? - routing - page models</a></li>
  <li><a href="/2021/08/18/fsharp-bolero-remoting.html">F# Bolero - A cóż to takiego? - remoting</a></li>
</ul>

<p>Aplikacja webowa składa się (w dużym uproszczeniu) z dwóch części:</p>

<ul>
  <li>Widoków</li>
  <li>Danych, prezentowanych na widokach</li>
</ul>

<p>Dane dla widoków możemy dostarczyć co najmniej na trzy sposoby:</p>

<ul>
  <li>Wołamy zewnętrzne <strong>API</strong> bezpośrednio z aplikacji</li>
  <li>Tworzymy własne <strong>API</strong>, które dla aplikacji będzie punktem wejścia do wołania innych <strong>API</strong></li>
  <li>Mix dwóch powyższych</li>
</ul>

<p>Tworząc własne <strong>API</strong> dla aplikacji pisanej w <a href="https://fsbolero.io/" title="F# Bolero">Bolero</a>, możemy skorzystać z mechanizmu <a href="https://fsbolero.io/docs/Remoting" title="F# Bolero Remoting">Remoting</a>, który w mojej subiektywnej opinii posiada trzy bardzo fajne właściwości:</p>

<ul>
  <li>Szczegóły komunikacji między klientem a serwerem ukryte są pod warstwą abstrakcji</li>
  <li>Ten sam sposób używania, niezależnie od tego, czy pobieramy, czy wysyłamy dane</li>
  <li>Dwa wynikowe stany - udało się lub nie</li>
</ul>

<p>W szczegółach, dane serilizowane są do formatu <strong>JSON</strong> oraz wymieniane przez protokół <strong>HTTP(s)</strong> z użyciem metody <strong>POST</strong>.</p>

<p>Zobaczmy, w jaki sposób możemy zaimplementować najprostsze <strong>API</strong> z jedną metodą, która zwraca tekst, a także, w jaki sposób możemy wywołać zaimplementowaną metodę.</p>

<p><a href="https://github.com/fsbolero/Template" title="F# Bolero Template">Bolero Template</a> umożliwia wygenerowanie kodu, gdzie serwer oraz klient skonfigurowani są w modelu <a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-5.0" title="Blazor hosting">hosted</a>. My zrealizujemy scenariusz, w którym klient oraz serwer hostowani są niezależnie od siebie.</p>

<p>Cały kod znajdziesz na <a href="https://github.com/mikedevbo/fsharp-bolero-sandbox" title="F# Bolero Sandbox">GitHub’e</a>.</p>

<p>Zacznijmy od utworzenia trzech projektów:</p>

<ul>
  <li>
    <p>kontrakty pomiędzy klientem, a serwerem - dll</p>

    <p><code class="language-plaintext highlighter-rouge">dotnet new classlib -n Remoting.Contracts -lang f#</code></p>
  </li>
  <li>
    <p>host serwera - ASP.NET Web API</p>

    <p><code class="language-plaintext highlighter-rouge">dotnet new webapi -n Remoting.Server -lang f#</code></p>
  </li>
  <li>
    <p>host klienta - <strong>Bolero</strong> minimal</p>

    <p><code class="language-plaintext highlighter-rouge">dotnet new bolero-app -n Remoting.Client --minimal=true --server=false</code></p>
  </li>
</ul>

<p>Następnie, w projekcie <strong>Remoting.Contracts</strong>:</p>

<ul>
  <li>dodajemy paczkę <strong>Bolero</strong></li>
</ul>

<p><code class="language-plaintext highlighter-rouge">dotnet add package Bolero</code></p>

<ul>
  <li>
    <p>usuwamy plik <code class="language-plaintext highlighter-rouge">Library.fs</code></p>
  </li>
  <li>
    <p>dodajemy plik <code class="language-plaintext highlighter-rouge">SimpleService.fs</code> z deklaracją <strong>API</strong></p>
  </li>
</ul>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">module</span> <span class="nn">Remoting</span><span class="p">.</span><span class="nc">Contracts</span>

<span class="k">open</span> <span class="nn">Bolero</span><span class="p">.</span><span class="nc">Remoting</span>

<span class="k">type</span> <span class="nc">SimpleService</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="nc">GetHello</span><span class="p">:</span> <span class="kt">unit</span> <span class="p">-&gt;</span> <span class="nc">Async</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;</span>
    <span class="p">}</span>

    <span class="k">interface</span> <span class="nc">IRemoteService</span> <span class="k">with</span>
        <span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">BasePath</span> <span class="p">=</span> <span class="s2">"/simpleservice"</span></code></pre></figure>

<p><strong>Record</strong> <code class="language-plaintext highlighter-rouge">SimpleService</code> zawiera pole <code class="language-plaintext highlighter-rouge">GetHello</code>, które reprezentuje metodę <strong>API</strong>. Wynikiem wywołania <code class="language-plaintext highlighter-rouge">GetHello</code> będzie zwrócenie przykładowego napisu. <code class="language-plaintext highlighter-rouge">SimpleService</code> dziedziczy po interfejsie <code class="language-plaintext highlighter-rouge">IRemoteService</code> z przestrzeni nazw <code class="language-plaintext highlighter-rouge">Bolero.Remoting</code>. Member <code class="language-plaintext highlighter-rouge">this.BasePath</code> wskazuje ścieżkę <strong>API</strong>. W ten sposób tworzymy deklarację, którą rozpozna <strong>Bolero</strong>.</p>

<p>Do projektu <strong>Remoting.Server</strong>:</p>

<ul>
  <li>dodajemy paczkę <strong>Bolero.Server</strong></li>
</ul>

<p><code class="language-plaintext highlighter-rouge">dotnet add package Bolero.Server</code></p>

<ul>
  <li>dodajemy referencję do projektu <strong>Remoting.Contracts</strong>:</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">dotnet add reference ../Remoting.Contracts</code></p>

<ul>
  <li>dodajemy plik <code class="language-plaintext highlighter-rouge">SimpleServiceHandler.fs</code> z implementacją <strong>API</strong></li>
</ul>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">namespace</span> <span class="nn">Remoting</span><span class="p">.</span><span class="nc">Server</span>

<span class="k">open</span> <span class="nn">Bolero</span><span class="p">.</span><span class="nn">Remoting</span><span class="p">.</span><span class="nc">Server</span>
<span class="k">open</span> <span class="nn">Microsoft</span><span class="p">.</span><span class="nn">AspNetCore</span><span class="p">.</span><span class="nc">Hosting</span>
<span class="k">open</span> <span class="nn">Remoting</span><span class="p">.</span><span class="nc">Contracts</span>
<span class="k">open</span> <span class="nn">Microsoft</span><span class="p">.</span><span class="nn">Extensions</span><span class="p">.</span><span class="nc">Primitives</span>

<span class="k">type</span> <span class="nc">SimpleServiceHandler</span><span class="p">(</span><span class="n">ctx</span><span class="p">:</span> <span class="nc">IRemoteContext</span><span class="p">,</span> <span class="n">env</span><span class="p">:</span> <span class="nc">IWebHostEnvironment</span><span class="p">)</span> <span class="p">=</span>
    <span class="k">inherit</span> <span class="nc">RemoteHandler</span><span class="p">&lt;</span><span class="nc">SimpleService</span><span class="o">&gt;()</span>

    <span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">Handler</span> <span class="p">=</span>
        <span class="p">{</span>
            <span class="nc">GetHello</span> <span class="p">=</span> <span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">async</span> <span class="p">{</span>
                <span class="n">ctx</span><span class="p">.</span><span class="nn">HttpContext</span><span class="p">.</span><span class="nn">Response</span><span class="p">.</span><span class="nn">Headers</span><span class="p">.</span><span class="nc">Add</span><span class="p">(</span><span class="s2">"Access-Control-Allow-Origin"</span><span class="p">,</span> <span class="nc">StringValues</span><span class="p">(</span><span class="s2">"*"</span><span class="o">))</span>
                <span class="k">return</span> <span class="s2">"Hello from Simple Service!"</span>
            <span class="p">}</span>
        <span class="p">}</span></code></pre></figure>

<p>Klasa <code class="language-plaintext highlighter-rouge">SimpleServiceHandler</code> dziedziczy po klasie <code class="language-plaintext highlighter-rouge">RemoteHandler</code> z przestrzeni nazw <code class="language-plaintext highlighter-rouge">Bolero.Remoting.Server</code>. Pod <code class="language-plaintext highlighter-rouge">Handler</code> podstawiamy definicję metody <strong>API</strong>. <strong>Bolero</strong> umożliwia skorzystanie z dwóch zależności, dla których dostarcza implementacje: <code class="language-plaintext highlighter-rouge">IRemoteContext</code> oraz <code class="language-plaintext highlighter-rouge">IWebHostEnvironment</code>. W przykładzie pierwszą z nich wykorzystujemy, aby do odpowiedzi <strong>HTTP(s)</strong> dodać nagłówek <code class="language-plaintext highlighter-rouge">Access-Control-Allow-Origin</code>.</p>

<ul>
  <li>w pliku <code class="language-plaintext highlighter-rouge">Startup.fs</code> konfigurujemy <strong>ASP.NET Web API</strong> włączając <strong>Bolero Remoting</strong> oraz definiując <strong>CORS</strong></li>
</ul>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Startup</span><span class="p">(</span><span class="n">configuration</span><span class="p">:</span> <span class="nc">IConfiguration</span><span class="p">)</span> <span class="p">=</span>
    <span class="k">member</span> <span class="o">_.</span><span class="nc">Configuration</span> <span class="p">=</span> <span class="n">configuration</span>

    <span class="c1">// This method gets called by the runtime. Use this method to add services to the container.</span>
    <span class="k">member</span> <span class="o">_.</span><span class="nc">ConfigureServices</span><span class="p">(</span><span class="n">services</span><span class="p">:</span> <span class="nc">IServiceCollection</span><span class="p">)</span> <span class="p">=</span>
        <span class="c1">// Add framework services.</span>
        <span class="n">services</span><span class="p">.</span><span class="nc">AddControllers</span><span class="bp">()</span> <span class="p">|&gt;</span> <span class="n">ignore</span>
        <span class="n">services</span><span class="p">.</span><span class="nc">AddRemoting</span><span class="p">&lt;</span><span class="nc">SimpleServiceHandler</span><span class="o">&gt;()</span>
                <span class="p">.</span><span class="nc">AddCors</span><span class="bp">()</span> <span class="p">|&gt;</span> <span class="n">ignore</span>

    <span class="c1">// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.</span>
    <span class="k">member</span> <span class="o">_.</span><span class="nc">Configure</span><span class="p">(</span><span class="n">app</span><span class="p">:</span> <span class="nc">IApplicationBuilder</span><span class="p">,</span> <span class="n">env</span><span class="p">:</span> <span class="nc">IWebHostEnvironment</span><span class="p">)</span> <span class="p">=</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">env</span><span class="p">.</span><span class="nc">IsDevelopment</span><span class="bp">()</span><span class="p">)</span> <span class="k">then</span>
            <span class="n">app</span><span class="p">.</span><span class="nc">UseDeveloperExceptionPage</span><span class="bp">()</span> <span class="p">|&gt;</span> <span class="n">ignore</span>
        <span class="n">app</span><span class="p">.</span><span class="nc">UseRemoting</span><span class="bp">()</span>
           <span class="p">.</span><span class="nc">UseHttpsRedirection</span><span class="bp">()</span>
           <span class="p">.</span><span class="nc">UseRouting</span><span class="bp">()</span>
           <span class="p">.</span><span class="nc">UseAuthorization</span><span class="bp">()</span>
           <span class="p">.</span><span class="nc">UseEndpoints</span><span class="p">(</span><span class="k">fun</span> <span class="n">endpoints</span> <span class="p">-&gt;</span>
                <span class="n">endpoints</span><span class="p">.</span><span class="nc">MapControllers</span><span class="bp">()</span> <span class="p">|&gt;</span> <span class="n">ignore</span>
            <span class="p">)</span>
           <span class="p">.</span><span class="nc">UseCors</span><span class="p">(</span><span class="k">fun</span> <span class="n">policy</span> <span class="p">-&gt;</span>
                <span class="n">policy</span><span class="p">.</span><span class="nc">AllowAnyOrigin</span><span class="bp">()</span>
                      <span class="p">.</span><span class="nc">AllowAnyMethod</span><span class="bp">()</span>
                      <span class="p">.</span><span class="nc">AllowAnyHeader</span><span class="bp">()</span> <span class="p">|&gt;</span> <span class="n">ignore</span>
             <span class="p">)</span> <span class="p">|&gt;</span> <span class="n">ignore</span></code></pre></figure>

<p><strong>Remoting</strong> włączamy poprzez wywołanie metod:</p>

<ul>
  <li>services.AddControllers()</li>
  <li>app.UseRemoting()</li>
</ul>

<p><strong>CORS</strong> definiujemy poprzez wywołanie metod:</p>

<ul>
  <li>services..AddCors()</li>
  <li>app.UseCors(…)</li>
</ul>

<p>W konfiguracji <a href="https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-5.0" title="Blazor hosting">hosted</a> <strong>CORS</strong> nie jest potrzebny, ponieważ zarówno serwer, jak i klient uruchamiane są z tej samej domeny.</p>

<p>Na koniec, w projekcie <strong>Remoting.Client</strong>:</p>

<ul>
  <li>dodajemy referencję do projektu <strong>Remoting.Contracts</strong>:</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">dotnet add reference ../../../Remoting.Contracts</code></p>

<ul>
  <li>w pliku <code class="language-plaintext highlighter-rouge">Startup.fs</code>, włączamy <strong>Bolero Remoting</strong> , podając bazowe <strong>URI</strong> pod którym dostępne jest <strong>API</strong></li>
</ul>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="n">builder</span><span class="p">.</span><span class="nn">Services</span><span class="p">.</span><span class="nc">AddRemoting</span><span class="o">((</span><span class="k">fun</span> <span class="n">httpClient</span> <span class="p">-&gt;</span>
    <span class="n">httpClient</span><span class="p">.</span><span class="nc">BaseAddress</span> <span class="p">&lt;-</span> <span class="nc">Uri</span><span class="p">(</span><span class="s2">"http://localhost:1234"</span><span class="o">)))</span> <span class="p">|&gt;</span> <span class="n">ignore</span></code></pre></figure>

<ul>
  <li>w pliku <code class="language-plaintext highlighter-rouge">main.fs</code>, realizujemy aplikację, które zawoła <strong>API</strong> za pomocą <a href="https://elmish.github.io/elmish/" title="Elmish">Elmish Commands</a>:</li>
</ul>

<p>Definiujemy model</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Model</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="nc">Text</span><span class="p">:</span> <span class="kt">string</span>
    <span class="p">}</span></code></pre></figure>

<p>Ustawiamy początkowe wartości modelu</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">initModel</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="nc">Text</span> <span class="p">=</span> <span class="s2">""</span>
    <span class="o">},</span> <span class="nn">Cmd</span><span class="p">.</span><span class="n">none</span></code></pre></figure>

<p>Nowością w stosunku do przykładów z poprzednich artykułów tej serii jest to, że wraz z wartościami modelu podajemy jaki <strong>Command</strong> ma się wykonać. W momencie inicjalizowania początkowych wartości nie wykonujemy żadnej akcji, oznaczając to przez <code class="language-plaintext highlighter-rouge">Cmd.none</code></p>

<p>Definiujemy Messages</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Message</span> <span class="p">=</span>
    <span class="p">|</span> <span class="nc">GetHello</span>
    <span class="p">|</span> <span class="nc">ShowHello</span> <span class="k">of</span> <span class="kt">string</span>
    <span class="p">|</span> <span class="nc">ShowError</span> <span class="k">of</span> <span class="n">exn</span></code></pre></figure>

<ul>
  <li><strong>GetHello</strong> - zawołanie <strong>API</strong></li>
  <li><strong>ShowHello</strong> - wykonanie działania, w przypadku, gdy <strong>API</strong> zwróci sukces</li>
  <li><strong>ShowError</strong> - wykonanie działania, w przypadku, gdy  <strong>API</strong> zwróci błąd</li>
</ul>

<p>Definiujemy funkcję update</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">update</span> <span class="n">simpleService</span> <span class="n">message</span> <span class="n">model</span> <span class="p">=</span>
    <span class="k">match</span> <span class="n">message</span> <span class="k">with</span>
    <span class="p">|</span> <span class="nc">GetHello</span> <span class="p">-&gt;</span>
        <span class="n">model</span><span class="p">,</span> <span class="nn">Cmd</span><span class="p">.</span><span class="nn">OfAsync</span><span class="p">.</span><span class="n">either</span> <span class="n">simpleService</span><span class="p">.</span><span class="nc">GetHello</span> <span class="bp">()</span> <span class="nc">ShowHello</span> <span class="nc">ShowError</span>
    <span class="p">|</span> <span class="nc">ShowHello</span> <span class="n">text</span> <span class="p">-&gt;</span>
        <span class="p">{</span><span class="n">model</span> <span class="k">with</span> <span class="nc">Text</span> <span class="p">=</span> <span class="n">text</span><span class="o">},</span> <span class="nn">Cmd</span><span class="p">.</span><span class="n">none</span>
    <span class="p">|</span> <span class="nc">ShowError</span> <span class="n">exn</span> <span class="p">-&gt;</span>
        <span class="p">{</span><span class="n">model</span> <span class="k">with</span> <span class="nc">Text</span> <span class="p">=</span> <span class="n">exn</span><span class="p">.</span><span class="nc">Message</span><span class="o">},</span> <span class="nn">Cmd</span><span class="p">.</span><span class="n">none</span></code></pre></figure>

<p>Oprócz standardowych parametrów <code class="language-plaintext highlighter-rouge">message</code> oraz <code class="language-plaintext highlighter-rouge">model</code>, funkcja przyjmuje parametr <code class="language-plaintext highlighter-rouge">simpleService</code>. Parametr jest typem <code class="language-plaintext highlighter-rouge">SimpleService</code> z projektu <code class="language-plaintext highlighter-rouge">Remoting.Contracts</code>. Po nadejściu Message’a <code class="language-plaintext highlighter-rouge">GetHello</code> wołamy <strong>API</strong>, za pomocą <strong>Command’a</strong> <code class="language-plaintext highlighter-rouge">Cmd.OfAsync.either</code>. W przypadku prawidłowego zakończenia wywołania zostanie zgłoszony Message <code class="language-plaintext highlighter-rouge">ShowHello</code> wpp. zostanie zgłoszony Message <code class="language-plaintext highlighter-rouge">ShowError</code>. Przy sukcesie wypełniamy model zwróconym rezultatem wpp. opisem błędu.</p>

<p>Definiujemy funkcję view</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">view</span> <span class="n">model</span> <span class="n">dispatch</span> <span class="p">=</span>
    <span class="n">concat</span> <span class="p">[</span>
        <span class="n">div</span> <span class="bp">[]</span> <span class="p">[</span>
            <span class="n">button</span> <span class="p">[</span><span class="n">on</span><span class="p">.</span><span class="n">click</span> <span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">GetHello</span><span class="o">)]</span> <span class="p">[</span><span class="n">text</span> <span class="s2">"GetHello"</span><span class="p">]</span>
        <span class="p">]</span>

        <span class="n">div</span> <span class="bp">[]</span> <span class="p">[</span>
            <span class="n">text</span> <span class="n">model</span><span class="p">.</span><span class="nc">Text</span>
        <span class="p">]</span>
    <span class="p">]</span></code></pre></figure>

<p>Widok zawiera dwa elementy:</p>

<ul>
  <li>przycisk z akcją <code class="language-plaintext highlighter-rouge">on.Click</code>, która wysyła Message <code class="language-plaintext highlighter-rouge">GetHello</code></li>
  <li>pole <code class="language-plaintext highlighter-rouge">text</code>, które wyświetla zawartość modelu</li>
</ul>

<p>Na koniec zmieniamy inicjalizowanie głównego komponentu z <code class="language-plaintext highlighter-rouge">mkSimple</code> na <code class="language-plaintext highlighter-rouge">mkProgram</code></p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">MyApp</span><span class="bp">()</span> <span class="p">=</span>
    <span class="k">inherit</span> <span class="nc">ProgramComponent</span><span class="p">&lt;</span><span class="nc">Model</span><span class="p">,</span> <span class="nc">Message</span><span class="o">&gt;()</span>

    <span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">Program</span> <span class="p">=</span>
        <span class="k">let</span> <span class="n">simpleService</span> <span class="p">=</span> <span class="n">this</span><span class="p">.</span><span class="nc">Remote</span><span class="p">&lt;</span><span class="nc">SimpleService</span><span class="o">&gt;()</span>
        <span class="nn">Program</span><span class="p">.</span><span class="n">mkProgram</span> <span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">initModel</span><span class="p">)</span> <span class="p">(</span><span class="n">update</span> <span class="n">simpleService</span><span class="p">)</span> <span class="n">view</span></code></pre></figure>

<p>W ten sposób wstrzykujemy klienta <strong>API</strong> do funkcji update oraz dostajemy możliwość używania <a href="https://elmish.github.io/elmish/" title="Elmish">Elmish Commands</a>.</p>

<h2 id="koniec-serii">Koniec Serii</h2>

<p>Tak, oto dotarliśmy do końca serii, która pokazuje podstawowe możliwości <a href="https://fsbolero.io/" title="F# Bolero">Bolero</a>. Dzięki przedstawionym mechanizmom możemy tworzyć rozwiązania webowe w podejściu <strong>SPA</strong>, pisząc kod w języku <strong>F#</strong>.</p>

<p><strong>Bolero</strong> posiada <a href="https://fsbolero.io/docs/" title="F# Bolero Documentation">o wiele więcej możliwości</a>. Jest to jednak temat na osobną serię artykułów.</p>

<p>Jeśli chodzi o mnie, to zostaję przy frameworku. Pamiętam, jak pierwszy raz przeczytałem o jego koncepcjach i doznałem tego fajnego efektu <em>“aha…ciekawe</em>…”. Teraz kiedy znam <strong>Bolero</strong> trochę lepiej, nic się nie zmieniło :)</p>

<p>Kolejnym etapem rozwoju jest wymyślenie tematu na aplikację oraz jej realizacja. Efektami prac podzielę się na blogu, którego w dalszej perspektywie będę chciał przepisać właśnie w <strong>Bolero</strong>.</p>

<p>=</p>

<h3> </h3>]]></content><author><name>mikedevbo</name></author><summary type="html"><![CDATA[Wstęp do F# Bolero. W tym artykule przeczytasz, w jaki sposób zaimplementować Remote Service.]]></summary></entry><entry><title type="html">F# Bolero - A cóż to takiego? - routing - page models</title><link href="/2021/06/12/fsharp-bolero-routing-page-models.html" rel="alternate" type="text/html" title="F# Bolero - A cóż to takiego? - routing - page models" /><published>2021-06-12T00:00:00+00:00</published><updated>2021-06-12T00:00:00+00:00</updated><id>/2021/06/12/fsharp-bolero-routing-page-models</id><content type="html" xml:base="/2021/06/12/fsharp-bolero-routing-page-models.html"><![CDATA[<p>Posty z tej serii:</p>

<ul>
  <li><a href="/2021/01/23/fsharp-bolero-introduction.html">F# Bolero - A cóż to takiego? - wprowadzenie</a></li>
  <li><a href="/2021/02/07/fsharp-bolero-html-templates.html">F# Bolero - A cóż to takiego? - HTML templates</a></li>
  <li><a href="/2021/02/28/fsharp-bolero-view-components.html">F# Bolero - A cóż to takiego? - view components</a></li>
  <li><a href="/2021/04/11/fsharp-bolero-routing.html">F# Bolero - A cóż to takiego? - routing</a></li>
  <li><a href="/2021/06/12/fsharp-bolero-routing-page-models.html">F# Bolero - A cóż to takiego? - routing - page models</a></li>
  <li><a href="/2021/08/18/fsharp-bolero-remoting.html">F# Bolero - A cóż to takiego? - remoting</a></li>
</ul>

<p><a href="https://fsbolero.io/" title="F# Bolero">Bolero</a> przetrzymuje stan, przez cały cykl życia aplikacji. Każda strona, symulowana za pomocą <a href="https://fsbolero.io/docs/Routing" title="F# Bolero Routing">Routingu</a>, ma dostęp do aktualnego stanu, co jest bardzo fajną właściwością. Poniższe nagranie pokazuje, jak to działa. Obserwuj dane, wprowadzane na każdej stronie:</p>

<video width="470" height="242" controls="" style="border-style: solid; border-width: thin;">
  <source src="/assets/fsharp_bolero/bolero_routing_nopagemodel.webm" type="video/webm" />
Your browser does not support the video tag.
</video>

<p>Korzystając z właściwości <a href="https://fsbolero.io/docs/Routing#page-models" title="F# Bolero Routing Page Model">Page Models</a>, możemy zaprogramować zachowanie, w którym, przy każdym wyświetlaniu strony, stan będzie ustawiany domyślnymi wartościami. Poniższe nagranie pokazuje, jak to działa. Ponownie, obserwuj dane, wprowadzane na każdej stronie:</p>

<video width="470" height="242" controls="" style="border-style: solid; border-width: thin;">
  <source src="/assets/fsharp_bolero/bolero_routing_pagemodel.webm" type="video/webm" />
Your browser does not support the video tag.
</video>

<p>Zobaczmy, w jaki sposób możemy zrealizować powyższą funkcjonalność. Cały kod znajdziesz na <a href="https://github.com/mikedevbo/fsharp-bolero-sandbox" title="F# Bolero Sandbox">GitHub’e</a>.</p>

<p>Tworzymy plik <code class="language-plaintext highlighter-rouge">main.html</code> z poniższym kodem i umieszczamy w katalogu <strong>wwwroot</strong>:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div&gt;</span>Choose:<span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"${MainLink}"</span><span class="nt">&gt;</span>Main<span class="nt">&lt;/a&gt;</span><span class="ni">&amp;nbsp;&amp;nbsp;</span>
<span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"${CounterLink}"</span><span class="nt">&gt;</span>Counter<span class="nt">&lt;/a&gt;</span><span class="ni">&amp;nbsp;&amp;nbsp;</span>
<span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"${EnterValuesLink}"</span><span class="nt">&gt;</span>EnterValues<span class="nt">&lt;/a&gt;</span><span class="ni">&amp;nbsp;&amp;nbsp;</span>
<span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"${ViewComponentsLink}"</span><span class="nt">&gt;</span>ViewComponents<span class="nt">&lt;/a&gt;</span><span class="ni">&amp;nbsp;&amp;nbsp;</span>
<span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
${Body}</code></pre></figure>

<p><strong>Holes</strong> zdefiniowane w atrybutach <code class="language-plaintext highlighter-rouge">href</code> reprezentują linki do poszczególnych stron. <code class="language-plaintext highlighter-rouge">$Body</code> reprezentuje zawartość aktualnie wyświetlanej strony.</p>

<p>Definiujemy <strong>Templates</strong> dla poszczególnych stron:</p>

<ul>
  <li>Counter - plik <strong>wwwroot/counter.html</strong></li>
</ul>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;span&gt;</span>Counter<span class="nt">&lt;/span&gt;</span>
<span class="nt">&lt;div&gt;</span>
    <span class="nt">&lt;button</span> <span class="na">onclick=</span><span class="s">"${Decrement}"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/button&gt;</span>
    <span class="nt">&lt;span&gt;</span>${Value}<span class="nt">&lt;/span&gt;</span>
    <span class="nt">&lt;button</span> <span class="na">onclick=</span><span class="s">"${Increment}"</span><span class="nt">&gt;</span>+<span class="nt">&lt;/button&gt;</span>
<span class="nt">&lt;/div&gt;</span></code></pre></figure>

<ul>
  <li>EnterValues - plik <strong>wwwroot/entervalues.html</strong></li>
</ul>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;span&gt;</span>EnterValues<span class="nt">&lt;/span&gt;</span>
<span class="nt">&lt;div&gt;</span>
    <span class="nt">&lt;label&gt;&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">bind=</span><span class="s">"${EnterValue}"</span><span class="nt">/&gt;&lt;/label&gt;</span>
    <span class="nt">&lt;span&gt;</span>${EnterValue}<span class="nt">&lt;/span&gt;</span>
<span class="nt">&lt;/div&gt;</span></code></pre></figure>

<ul>
  <li>ViewComponents - plik <strong>wwwroot/viewcomponents.html</strong></li>
</ul>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;span&gt;</span>ViewComponents<span class="nt">&lt;/span&gt;</span>
<span class="nt">&lt;div&gt;</span>
    <span class="nt">&lt;label&gt;&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">bind=</span><span class="s">"${ViewComponentValue}"</span><span class="nt">/&gt;&lt;/label&gt;</span>
    <span class="nt">&lt;span&gt;</span>${ViewComponentValue}<span class="nt">&lt;/span&gt;</span>
<span class="nt">&lt;/div&gt;</span></code></pre></figure>

<p>Definiujemy model oraz stan początkowy dla poszczególnych stron:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">CounterModel</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">value</span><span class="p">:</span> <span class="kt">int</span>
    <span class="p">}</span>

<span class="k">type</span> <span class="nc">EnterValuesModel</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">value</span><span class="p">:</span> <span class="kt">string</span>
    <span class="p">}</span>

<span class="k">type</span> <span class="nc">ViewComponentsModel</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">value</span><span class="p">:</span> <span class="kt">string</span>
    <span class="p">}</span>

<span class="k">let</span> <span class="n">defaultModel</span> <span class="p">=</span> <span class="k">function</span>
    <span class="p">|</span> <span class="nc">Main</span> <span class="p">-&gt;</span> <span class="bp">()</span>
    <span class="p">|</span> <span class="nc">Counter</span> <span class="n">model</span> <span class="p">-&gt;</span> <span class="nn">Router</span><span class="p">.</span><span class="n">definePageModel</span> <span class="n">model</span> <span class="p">{</span> <span class="n">value</span> <span class="p">=</span> <span class="mi">0</span> <span class="p">}</span>
    <span class="p">|</span> <span class="nc">EnterValues</span> <span class="n">model</span> <span class="p">-&gt;</span> <span class="nn">Router</span><span class="p">.</span><span class="n">definePageModel</span> <span class="n">model</span> <span class="p">{</span> <span class="n">value</span> <span class="p">=</span> <span class="s2">""</span> <span class="p">}</span>
    <span class="p">|</span> <span class="nc">ViewComponents</span> <span class="n">model</span> <span class="p">-&gt;</span> <span class="nn">Router</span><span class="p">.</span><span class="n">definePageModel</span> <span class="n">model</span> <span class="p">{</span> <span class="n">value</span> <span class="p">=</span> <span class="s2">""</span> <span class="p">}</span>    </code></pre></figure>

<p>Do zdefiniowania początkowego stanu wykorzystujemy funkcję <code class="language-plaintext highlighter-rouge">Router.definePageModel</code></p>

<p>Modelujemy dostępne strony z odpowiednim <strong>Page Model</strong>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Page</span> <span class="p">=</span>
    <span class="p">|</span> <span class="p">[&lt;</span><span class="nc">EndPoint</span> <span class="s2">"/"</span><span class="p">&gt;]</span> <span class="nc">Main</span>
    <span class="p">|</span> <span class="p">[&lt;</span><span class="nc">EndPoint</span> <span class="s2">"/counter"</span><span class="p">&gt;]</span> <span class="nc">Counter</span> <span class="k">of</span> <span class="nc">PageModel</span><span class="p">&lt;</span><span class="nc">CounterModel</span><span class="p">&gt;</span>
    <span class="p">|</span> <span class="p">[&lt;</span><span class="nc">EndPoint</span> <span class="s2">"/entervalues"</span><span class="p">&gt;]</span> <span class="nc">EnterValues</span> <span class="k">of</span> <span class="nc">PageModel</span><span class="p">&lt;</span><span class="nc">EnterValuesModel</span><span class="p">&gt;</span>
    <span class="p">|</span> <span class="p">[&lt;</span><span class="nc">EndPoint</span> <span class="s2">"/viewcomponents"</span><span class="p">&gt;]</span> <span class="nc">ViewComponents</span> <span class="k">of</span> <span class="nc">PageModel</span><span class="p">&lt;</span><span class="nc">ViewComponentsModel</span><span class="p">&gt;</span></code></pre></figure>

<p>Definiujemy główny model oraz ustawiamy stan początkowy dla całej aplikacji:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Model</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">page</span><span class="p">:</span> <span class="nc">Page</span>
    <span class="p">}</span>

<span class="k">let</span> <span class="n">initModel</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">page</span> <span class="p">=</span> <span class="nc">Main</span>
    <span class="p">}</span></code></pre></figure>

<p>Stan poszczególnych stron został przeniesiony do <strong>Page Models</strong>. Główny model aplikacji zawiera informację o dostępnych stronach.</p>

<p>Definiujemy <strong>Message</strong> dla poszczególnych akcji:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Message</span> <span class="p">=</span>
    <span class="p">|</span> <span class="nc">SetPage</span> <span class="k">of</span> <span class="nc">Page</span>
    <span class="p">|</span> <span class="nc">Increment</span>
    <span class="p">|</span> <span class="nc">Decrement</span>
    <span class="p">|</span> <span class="nc">EnterValue</span> <span class="k">of</span> <span class="kt">string</span>
    <span class="p">|</span> <span class="nc">EnterViewComponentValue</span> <span class="k">of</span> <span class="kt">string</span></code></pre></figure>

<p>Definiujemy <strong>Routing</strong>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">router</span> <span class="p">=</span> <span class="nn">Router</span><span class="p">.</span><span class="n">inferWithModel</span> <span class="nc">SetPage</span> <span class="p">(</span><span class="k">fun</span> <span class="n">m</span> <span class="p">-&gt;</span> <span class="n">m</span><span class="p">.</span><span class="n">page</span><span class="p">)</span> <span class="n">defaultModel</span></code></pre></figure>

<p>W zwykłym <strong>Routingu</strong> używamy funkcji <code class="language-plaintext highlighter-rouge">Router.infer</code>. W <strong>Routingu</strong> z modelem używamy funkcji <code class="language-plaintext highlighter-rouge">Router.inferWithModel</code>. Trzeci parametr funkcji przyjmuje definicję domyślnych wartości dla poszczególnych stron.</p>

<p>Definiujemy funkcję <code class="language-plaintext highlighter-rouge">update</code>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">update</span> <span class="n">message</span> <span class="n">model</span> <span class="p">=</span>
    <span class="k">match</span> <span class="n">message</span> <span class="k">with</span>
    <span class="p">|</span> <span class="nc">SetPage</span> <span class="n">p</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">page</span> <span class="p">=</span> <span class="n">p</span> <span class="p">}</span>
    <span class="p">|</span> <span class="nc">Increment</span> <span class="p">-&gt;</span>
        <span class="k">match</span> <span class="n">model</span><span class="p">.</span><span class="n">page</span> <span class="k">with</span>
        <span class="p">|</span> <span class="nc">Counter</span> <span class="n">pageModel</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">page</span> <span class="p">=</span> <span class="nc">Counter</span> <span class="p">{</span> <span class="nc">Model</span> <span class="p">=</span> <span class="p">{</span> <span class="n">pageModel</span><span class="p">.</span><span class="nc">Model</span> <span class="k">with</span> <span class="n">value</span> <span class="p">=</span> <span class="n">pageModel</span><span class="p">.</span><span class="nn">Model</span><span class="p">.</span><span class="n">value</span> <span class="o">+</span> <span class="mi">1</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
        <span class="p">|</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">model</span>
    <span class="p">|</span> <span class="nc">Decrement</span> <span class="p">-&gt;</span>
        <span class="k">match</span> <span class="n">model</span><span class="p">.</span><span class="n">page</span> <span class="k">with</span>
        <span class="p">|</span> <span class="nc">Counter</span> <span class="n">pageModel</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">page</span> <span class="p">=</span> <span class="nc">Counter</span> <span class="p">{</span> <span class="nc">Model</span> <span class="p">=</span> <span class="p">{</span> <span class="n">pageModel</span><span class="p">.</span><span class="nc">Model</span> <span class="k">with</span> <span class="n">value</span> <span class="p">=</span> <span class="n">pageModel</span><span class="p">.</span><span class="nn">Model</span><span class="p">.</span><span class="n">value</span> <span class="p">-</span> <span class="mi">1</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
        <span class="p">|</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">model</span>
    <span class="p">|</span> <span class="nc">EnterValue</span> <span class="n">value</span> <span class="p">-&gt;</span>
        <span class="k">match</span> <span class="n">model</span><span class="p">.</span><span class="n">page</span> <span class="k">with</span>
        <span class="p">|</span> <span class="nc">EnterValues</span> <span class="n">pageModel</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">page</span> <span class="p">=</span> <span class="nc">EnterValues</span> <span class="p">{</span> <span class="nc">Model</span> <span class="p">=</span> <span class="p">{</span> <span class="n">pageModel</span><span class="p">.</span><span class="nc">Model</span> <span class="k">with</span> <span class="n">value</span> <span class="p">=</span> <span class="n">value</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
        <span class="p">|</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">model</span>
    <span class="p">|</span> <span class="nc">EnterViewComponentValue</span> <span class="n">value</span> <span class="p">-&gt;</span>
        <span class="k">match</span> <span class="n">model</span><span class="p">.</span><span class="n">page</span> <span class="k">with</span>
        <span class="p">|</span> <span class="nc">ViewComponents</span> <span class="n">pageModel</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">page</span> <span class="p">=</span> <span class="nc">ViewComponents</span> <span class="p">{</span> <span class="nc">Model</span> <span class="p">=</span> <span class="p">{</span> <span class="n">pageModel</span><span class="p">.</span><span class="nc">Model</span> <span class="k">with</span> <span class="n">value</span> <span class="p">=</span> <span class="n">value</span> <span class="p">}</span> <span class="p">}</span> <span class="p">}</span>
        <span class="p">|</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">model</span></code></pre></figure>

<p>W naszym przykładzie każdy <strong>Message</strong> może być wykonany na jednej, konkretnej stronie. W związku z tym, w pierwszej kolejności rozpoznajemy, czy jesteśmy na właściwej stronie - <code class="language-plaintext highlighter-rouge">match model.page with</code> - następnie wykonujemy logikę działania.</p>

<p>Parametr <code class="language-plaintext highlighter-rouge">pageModel</code> reprezentuje aktualny stan na stronie. <strong>Routing</strong> z <strong>Page Models</strong> zapewnia resetowanie stanu do wartości domyślnych, przy przechodzeniu pomiędzy stronami.</p>

<p>Definiujemy <strong>View Component</strong>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">InputComponent</span><span class="bp">()</span> <span class="p">=</span>
    <span class="k">inherit</span> <span class="nc">ElmishComponent</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="o">&gt;()</span>

    <span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">ShouldRender</span><span class="p">(</span><span class="n">oldModel</span><span class="p">,</span> <span class="n">newModel</span><span class="p">)</span> <span class="p">=</span>
        <span class="n">oldModel</span> <span class="p">&lt;&gt;</span> <span class="n">newModel</span>

    <span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">View</span> <span class="n">model</span> <span class="n">dispatch</span> <span class="p">=</span>
        <span class="nc">ViewComponentsTemplate</span><span class="bp">()</span>
            <span class="p">.</span><span class="nc">ViewComponentValue</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="k">fun</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="n">value</span><span class="p">)</span>
            <span class="p">.</span><span class="nc">Elt</span><span class="bp">()</span></code></pre></figure>

<p>Definiujemy funkcję <strong>view</strong>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">view</span> <span class="n">model</span> <span class="n">dispatch</span> <span class="p">=</span>
    <span class="k">let</span> <span class="n">main</span> <span class="p">=</span>
        <span class="nc">MainTemplate</span><span class="bp">()</span>
            <span class="p">.</span><span class="nc">MainLink</span><span class="p">(</span><span class="n">router</span><span class="p">.</span><span class="nc">Link</span> <span class="nc">Main</span><span class="p">)</span>
            <span class="p">.</span><span class="nc">CounterLink</span><span class="p">(</span><span class="n">router</span><span class="p">.</span><span class="nc">Link</span> <span class="p">(</span><span class="nc">Counter</span> <span class="nn">Router</span><span class="p">.</span><span class="n">noModel</span><span class="o">))</span>
            <span class="p">.</span><span class="nc">EnterValuesLink</span><span class="p">(</span><span class="n">router</span><span class="p">.</span><span class="nc">Link</span> <span class="p">(</span><span class="nc">EnterValues</span> <span class="nn">Router</span><span class="p">.</span><span class="n">noModel</span><span class="o">))</span>
            <span class="p">.</span><span class="nc">ViewComponentsLink</span><span class="p">(</span><span class="n">router</span><span class="p">.</span><span class="nc">Link</span> <span class="p">(</span><span class="nc">ViewComponents</span> <span class="nn">Router</span><span class="p">.</span><span class="n">noModel</span><span class="o">))</span>

    <span class="k">let</span> <span class="n">counter</span> <span class="p">(</span><span class="n">pageModel</span><span class="p">:</span> <span class="nc">CounterModel</span><span class="p">)</span> <span class="p">=</span>
        <span class="nc">CounterTemplate</span><span class="bp">()</span>
            <span class="p">.</span><span class="nc">Value</span><span class="p">(</span><span class="kt">string</span> <span class="n">pageModel</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>
            <span class="p">.</span><span class="nc">Increment</span><span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">Increment</span><span class="p">)</span>
            <span class="p">.</span><span class="nc">Decrement</span><span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">Decrement</span><span class="p">)</span>

    <span class="k">let</span> <span class="n">enterValues</span> <span class="p">(</span><span class="n">pageModel</span><span class="p">:</span> <span class="nc">EnterValuesModel</span><span class="p">)</span> <span class="p">=</span>
        <span class="nc">EnterValuesTemplate</span><span class="bp">()</span>
            <span class="p">.</span><span class="nc">EnterValue</span><span class="p">(</span><span class="n">pageModel</span><span class="p">.</span><span class="n">value</span><span class="p">,</span> <span class="k">fun</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="p">(</span><span class="nc">EnterValue</span> <span class="n">value</span><span class="o">))</span>

    <span class="k">let</span> <span class="n">viewComponents</span> <span class="p">(</span><span class="n">pageModel</span><span class="p">:</span> <span class="nc">ViewComponentsModel</span><span class="p">)</span> <span class="p">=</span>
        <span class="n">ecomp</span><span class="p">&lt;</span><span class="nc">InputComponent</span><span class="p">,</span> <span class="o">_,</span> <span class="o">_&gt;</span> <span class="bp">[]</span> <span class="n">pageModel</span><span class="p">.</span><span class="n">value</span> <span class="p">(</span><span class="k">fun</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="p">(</span><span class="nc">EnterViewComponentValue</span> <span class="n">value</span><span class="o">))</span>

    <span class="k">match</span> <span class="n">model</span><span class="p">.</span><span class="n">page</span> <span class="k">with</span>
    <span class="p">|</span> <span class="nc">Main</span> <span class="p">-&gt;</span>
        <span class="n">main</span><span class="p">.</span><span class="nc">Body</span><span class="p">(</span><span class="s2">""</span><span class="o">).</span><span class="nc">Elt</span><span class="bp">()</span>
    <span class="p">|</span> <span class="nc">Counter</span> <span class="n">pageModel</span> <span class="p">-&gt;</span>
        <span class="n">main</span><span class="p">.</span><span class="nc">Body</span><span class="o">((</span><span class="n">counter</span> <span class="n">pageModel</span><span class="p">.</span><span class="nc">Model</span><span class="o">).</span><span class="nc">Elt</span><span class="bp">()</span><span class="o">).</span><span class="nc">Elt</span><span class="bp">()</span>
    <span class="p">|</span> <span class="nc">EnterValues</span> <span class="n">pageModel</span> <span class="p">-&gt;</span>
        <span class="n">main</span><span class="p">.</span><span class="nc">Body</span><span class="o">((</span><span class="n">enterValues</span> <span class="n">pageModel</span><span class="p">.</span><span class="nc">Model</span><span class="o">).</span><span class="nc">Elt</span><span class="bp">()</span><span class="o">).</span><span class="nc">Elt</span><span class="bp">()</span>
    <span class="p">|</span> <span class="nc">ViewComponents</span> <span class="n">pageModel</span><span class="p">-&gt;</span>
        <span class="n">main</span><span class="p">.</span><span class="nc">Body</span><span class="o">((</span><span class="n">viewComponents</span> <span class="n">pageModel</span><span class="p">.</span><span class="nc">Model</span><span class="o">)).</span><span class="nc">Elt</span><span class="bp">()</span></code></pre></figure>

<p>Podobnie, jak w przypadku funkcji <code class="language-plaintext highlighter-rouge">update</code>, w pierwszej kolejności musimy rozpoznać, która strona ma być wyświetlona - <code class="language-plaintext highlighter-rouge">match model.page with</code>. Pomocnicze funkcje <code class="language-plaintext highlighter-rouge">counter</code>, <code class="language-plaintext highlighter-rouge">enterValues</code> oraz <code class="language-plaintext highlighter-rouge">viewComponents</code> renderują odpowiedni <strong>HTML Template</strong>. Każda funkcja przyjmuje w parametrze model, należący do danej strony. Wartości modelu wstawiane są w odpowiednie <strong>Holes</strong>.</p>

<p>Na koniec definiujemy start aplikacji:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">MyApp</span><span class="bp">()</span> <span class="p">=</span>
    <span class="k">inherit</span> <span class="nc">ProgramComponent</span><span class="p">&lt;</span><span class="nc">Model</span><span class="p">,</span> <span class="nc">Message</span><span class="o">&gt;()</span>

    <span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">Program</span> <span class="p">=</span>
        <span class="nn">Program</span><span class="p">.</span><span class="n">mkSimple</span> <span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">initModel</span><span class="p">)</span> <span class="n">update</span> <span class="n">view</span>
        <span class="p">|&gt;</span> <span class="nn">Program</span><span class="p">.</span><span class="n">withRouter</span> <span class="n">router</span></code></pre></figure>

<p>To tyle, jeśli chodzi o programowanie w <strong>Bolero</strong> z użyciem <strong>Page Models</strong>.</p>

<p>Poznane do tej pory właściwości, pozwalają programować tzw. <strong>Frontend</strong>. Okazuje się, że <strong>Bolero</strong> posiada dodatkową cechę zwaną <strong>Remoting</strong>, która pozwala kodować punkt wejścia do <strong>Backendu</strong>. Przyjrzymy się temu w następnym artykule.</p>

<p>=</p>

<h3> </h3>]]></content><author><name>mikedevbo</name></author><summary type="html"><![CDATA[Wstęp do F# Bolero. W tym artykule przeczytasz, w jaki sposób używać Page Models do specyfikowania modelu danych dla konkretnej strony.]]></summary></entry><entry><title type="html">F# Bolero - A cóż to takiego? - routing</title><link href="/2021/04/11/fsharp-bolero-routing.html" rel="alternate" type="text/html" title="F# Bolero - A cóż to takiego? - routing" /><published>2021-04-11T00:00:00+00:00</published><updated>2021-04-11T00:00:00+00:00</updated><id>/2021/04/11/fsharp-bolero-routing</id><content type="html" xml:base="/2021/04/11/fsharp-bolero-routing.html"><![CDATA[<p>Posty z tej serii:</p>

<ul>
  <li><a href="/2021/01/23/fsharp-bolero-introduction.html">F# Bolero - A cóż to takiego? - wprowadzenie</a></li>
  <li><a href="/2021/02/07/fsharp-bolero-html-templates.html">F# Bolero - A cóż to takiego? - HTML templates</a></li>
  <li><a href="/2021/02/28/fsharp-bolero-view-components.html">F# Bolero - A cóż to takiego? - view components</a></li>
  <li><a href="/2021/04/11/fsharp-bolero-routing.html">F# Bolero - A cóż to takiego? - routing</a></li>
  <li><a href="/2021/06/12/fsharp-bolero-routing-page-models.html">F# Bolero - A cóż to takiego? - routing - page models</a></li>
  <li><a href="/2021/08/18/fsharp-bolero-remoting.html">F# Bolero - A cóż to takiego? - remoting</a></li>
</ul>

<p>Wspólną cechą omawianych do tej pory przykładów jest to, że dane wyświetlane są na jednej stronie. W rzeczywistości systemy webowe zawierają wiele stron. Adres aktualnie przeglądanej strony pokazywany jest w pasku przeglądarki internetowej. W <strong>Bolero</strong> taki efekt można uzyskać, wykorzystując mechanizm o nazwie <a href="https://fsbolero.io/docs/Routing" title="F# Bolero Routing">Routing</a>.</p>

<p><strong>Bolero</strong> nie definiuje pojęcia strony. Mamy do dyspozycji widok, który sami musimy zaprojektować pod wymagane potrzeby. Weźmy dla przykładu aplikację, która zawiera trzy przyciski:</p>

<ul>
  <li>Counter</li>
  <li>EnterValues</li>
  <li>ViewComponents</li>
</ul>

<p>Po kliknięciu odpowiedniego przycisku, widok powinien pokazać jedną z funkcjonalności omawianych w poprzednich artykułach. Taki efekt możemy uzyskać, modelując kliknięty przycisk, a następnie na podstawie wartości modelu, wyświetlić prawidłowy HTML template. W ostateczności widok będzie się zmieniał, ale adres w pasku przeglądarki pozostanie ten sam. Dla uproszczenia poniższy przykład wyświetla prosty napis załadowanej funkcjonalności:</p>

<p><img src="/assets/fsharp_bolero/bolero_routing_site_address.png" alt="Picture1" />
<br />
<img src="/assets/fsharp_bolero/bolero_routing_counter.png" alt="Picture2" />
<br />
<br />
<img src="/assets/fsharp_bolero/bolero_routing_site_address.png" alt="Picture3" />
<br />
<img src="/assets/fsharp_bolero/bolero_routing_entervalues.png" alt="Picture4" />
<br />
<br />
<img src="/assets/fsharp_bolero/bolero_routing_site_address.png" alt="Picture5" />
<br />
<img src="/assets/fsharp_bolero/bolero_routing_viewcomponents.png" alt="Picture6" /></p>

<p>Jeśli chcemy, aby każdy z wyświetlanych widoków posiadał swój unikalny adres, wyświetlany w pasku przeglądarki, musimy skorzystać z mechanizmu <strong>Routing’u</strong>.</p>

<p>Zaprojektujmy oraz zaprogramujmy taki efekt. Zamienimy jedną rzecz w stosunku do powyższego przykładu - zamiast wyświetlać przyciski, wyświetlimy linki. Dzięki temu zobaczymy, w jaki sposób możemy skorzystać z jednej z właściwości <strong>Routing’u</strong> pozwalającej na pobranie adresu URL dla wybranej strony.</p>

<p>Kod opisanych przykładów znajdziesz na <a href="https://github.com/mikedevbo/fsharp-bolero-sandbox" title="F# Bolero Sandbox">GitHub’e</a>.</p>

<p>Końcowy wynik będzie wyglądał tak:</p>

<p><img src="/assets/fsharp_bolero/bolero_routing_site_address.png" alt="Picture7" />
<br />
<img src="/assets/fsharp_bolero/bolero_routing_link_main.png" alt="Picture8" />
<br />
<br />
<img src="/assets/fsharp_bolero/bolero_routing_link_counter_address.png" alt="Picture9" />
<br />
<img src="/assets/fsharp_bolero/bolero_routing_link_counter.png" alt="Picture10" />
<br />
<br />
<img src="/assets/fsharp_bolero/bolero_routing_link_entervalues_address.png" alt="Picture11" />
<br />
<img src="/assets/fsharp_bolero/bolero_routing_link_entervalues.png" alt="Picture12" />
<br />
<br />
<img src="/assets/fsharp_bolero/bolero_routing_link_viewcomponents_address.png" alt="Picture13" />
<br />
<img src="/assets/fsharp_bolero/bolero_routing_link_viewcomponents.png" alt="Picture14" />
<br />
<br /></p>

<p>W pierwszym kroku tworzymy plik <code class="language-plaintext highlighter-rouge">main.html</code> z poniższym kodem i umieszczamy w katalogu <strong>wwwroot</strong>:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div&gt;</span>Choose:<span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"${MainLink}"</span><span class="nt">&gt;</span>Main<span class="nt">&lt;/a&gt;</span><span class="ni">&amp;nbsp;&amp;nbsp;</span>
<span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"${CounterLink}"</span><span class="nt">&gt;</span>Counter<span class="nt">&lt;/a&gt;</span><span class="ni">&amp;nbsp;&amp;nbsp;</span>
<span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"${EnterValuesLink}"</span><span class="nt">&gt;</span>EnterValues<span class="nt">&lt;/a&gt;</span><span class="ni">&amp;nbsp;&amp;nbsp;</span>
<span class="nt">&lt;a</span> <span class="na">href=</span><span class="s">"${ViewComponentsLink}"</span><span class="nt">&gt;</span>ViewComponents<span class="nt">&lt;/a&gt;</span><span class="ni">&amp;nbsp;&amp;nbsp;</span>
<span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
${Body}</code></pre></figure>

<p><strong>Holes</strong> zdefiniowane w atrybutach <code class="language-plaintext highlighter-rouge">href</code> reprezentują linki do poszczególnych stron. <code class="language-plaintext highlighter-rouge">$Body</code> reprezentuje zawartość aktualnie wyświetlanej strony.</p>

<p>Definiujemy proste <strong>Templates</strong> dla poszczególnych stron:</p>

<ul>
  <li>Counter - plik <strong>wwwroot/counter.html</strong></li>
</ul>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;span&gt;</span>Counter<span class="nt">&lt;/span&gt;</span></code></pre></figure>

<ul>
  <li>EnterValues - plik <strong>wwwroot/entervalues.html</strong></li>
</ul>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;span&gt;</span>EnterValues<span class="nt">&lt;/span&gt;</span></code></pre></figure>

<ul>
  <li>ViewComponents - plik <strong>wwwroot/viewcomponents.html</strong></li>
</ul>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;span&gt;</span>ViewComponents<span class="nt">&lt;/span&gt;</span></code></pre></figure>

<p>Modelujemy dostępne strony oraz ustawiamy stronę startową:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Page</span> <span class="p">=</span>
    <span class="p">|</span> <span class="p">[&lt;</span><span class="nc">EndPoint</span> <span class="s2">"/"</span><span class="p">&gt;]</span> <span class="nc">Main</span>
    <span class="p">|</span> <span class="p">[&lt;</span><span class="nc">EndPoint</span> <span class="s2">"/counter"</span><span class="p">&gt;]</span> <span class="nc">Counter</span>
    <span class="p">|</span> <span class="p">[&lt;</span><span class="nc">EndPoint</span> <span class="s2">"/entervalues"</span><span class="p">&gt;]</span> <span class="nc">EnterValues</span>
    <span class="p">|</span> <span class="p">[&lt;</span><span class="nc">EndPoint</span> <span class="s2">"/viewcomponents"</span><span class="p">&gt;]</span> <span class="nc">ViewComponents</span>

<span class="k">type</span> <span class="nc">Model</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">page</span><span class="p">:</span> <span class="nc">Page</span>
    <span class="p">}</span>

<span class="k">let</span> <span class="n">initModel</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">page</span> <span class="p">=</span> <span class="nc">Main</span>
    <span class="p">}</span></code></pre></figure>

<p><strong>Union Type</strong> <code class="language-plaintext highlighter-rouge">Page</code> reprezentuje strony. Atrybut <code class="language-plaintext highlighter-rouge">EntryPoint</code> pozwala konstruować wzorce dla ścieżki <code class="language-plaintext highlighter-rouge">URL</code>. Przykładowo definicja <code class="language-plaintext highlighter-rouge">[&lt;EndPoint "/counter/{id}"&gt;]</code> wymagałby podania w URL <code class="language-plaintext highlighter-rouge">id</code> licznika. W naszym przykładzie nie są wymagane żadne parametry. W takim przypadku można pominąć atrybut <code class="language-plaintext highlighter-rouge">EntryPoint</code>, ale zostawiamy go dla lepszej czytelności.</p>

<p>Główną stroną jest strona, która nie wyświetla nic (patrz screen powyżej).</p>

<p>Definiujemy <strong>message</strong>, który będzie ustawiał stronę:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Message</span> <span class="p">=</span>
    <span class="p">|</span> <span class="nc">SetPage</span> <span class="k">of</span> <span class="nc">Page</span></code></pre></figure>

<p>Definiujemy logikę funkcji <code class="language-plaintext highlighter-rouge">update</code>, która zaktualizuje stan o wybraną stronę:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">update</span> <span class="n">message</span> <span class="n">model</span> <span class="p">=</span>
    <span class="k">match</span> <span class="n">message</span> <span class="k">with</span>
    <span class="p">|</span> <span class="nc">SetPage</span> <span class="n">p</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">page</span> <span class="p">=</span> <span class="n">p</span> <span class="p">}</span></code></pre></figure>

<p>Definiujemy <strong>Type Templates</strong> dla stron:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">MainTemplate</span> <span class="p">=</span> <span class="nc">Template</span><span class="p">&lt;</span><span class="s2">"wwwroot/main.html"</span><span class="p">&gt;</span>
<span class="k">type</span> <span class="nc">CounterTemplate</span> <span class="p">=</span> <span class="nc">Template</span><span class="p">&lt;</span><span class="s2">"wwwroot/counter.html"</span><span class="p">&gt;</span>
<span class="k">type</span> <span class="nc">EnterValuesTemplate</span> <span class="p">=</span> <span class="nc">Template</span><span class="p">&lt;</span><span class="s2">"wwwroot/entervalues.html"</span><span class="p">&gt;</span>
<span class="k">type</span> <span class="nc">ViewComponentsTemplate</span> <span class="p">=</span> <span class="nc">Template</span><span class="p">&lt;</span><span class="s2">"wwwroot/viewcomponents.html"</span><span class="p">&gt;</span></code></pre></figure>

<p>Definiujemy funkcję <code class="language-plaintext highlighter-rouge">view</code>, która wyświetli widok w zależności od stanu modelu:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">view</span> <span class="n">model</span> <span class="n">dispatch</span> <span class="p">=</span>
    <span class="k">let</span> <span class="n">main</span> <span class="p">=</span>
        <span class="nc">MainTemplate</span><span class="bp">()</span>
            <span class="p">.</span><span class="nc">MainLink</span><span class="p">(</span><span class="n">router</span><span class="p">.</span><span class="nc">Link</span> <span class="nc">Main</span><span class="p">)</span>
            <span class="p">.</span><span class="nc">CounterLink</span><span class="p">(</span><span class="n">router</span><span class="p">.</span><span class="nc">Link</span> <span class="nc">Counter</span><span class="p">)</span>
            <span class="p">.</span><span class="nc">EnterValuesLink</span><span class="p">(</span><span class="n">router</span><span class="p">.</span><span class="nc">Link</span> <span class="nc">EnterValues</span><span class="p">)</span>
            <span class="p">.</span><span class="nc">ViewComponentsLink</span><span class="p">(</span><span class="n">router</span><span class="p">.</span><span class="nc">Link</span> <span class="nc">ViewComponents</span><span class="p">)</span>

    <span class="k">match</span> <span class="n">model</span><span class="p">.</span><span class="n">page</span> <span class="k">with</span>
    <span class="p">|</span> <span class="nc">Main</span> <span class="p">-&gt;</span>
        <span class="n">main</span><span class="p">.</span><span class="nc">Body</span><span class="p">(</span><span class="s2">""</span><span class="o">).</span><span class="nc">Elt</span><span class="bp">()</span>
    <span class="p">|</span> <span class="nc">Counter</span> <span class="p">-&gt;</span>
        <span class="n">main</span><span class="p">.</span><span class="nc">Body</span><span class="p">(</span><span class="nc">CounterTemplate</span><span class="bp">()</span><span class="p">.</span><span class="nc">Elt</span><span class="bp">()</span><span class="o">).</span><span class="nc">Elt</span><span class="bp">()</span>
    <span class="p">|</span> <span class="nc">EnterValues</span> <span class="p">-&gt;</span>
        <span class="n">main</span><span class="p">.</span><span class="nc">Body</span><span class="p">(</span><span class="nc">EnterValuesTemplate</span><span class="bp">()</span><span class="p">.</span><span class="nc">Elt</span><span class="bp">()</span><span class="o">).</span><span class="nc">Elt</span><span class="bp">()</span>
    <span class="p">|</span> <span class="nc">ViewComponents</span> <span class="p">-&gt;</span>
        <span class="n">main</span><span class="p">.</span><span class="nc">Body</span><span class="p">(</span><span class="nc">ViewComponentsTemplate</span><span class="bp">()</span><span class="p">.</span><span class="nc">Elt</span><span class="bp">()</span><span class="o">).</span><span class="nc">Elt</span><span class="bp">()</span></code></pre></figure>

<p>Pomocnicza funkcja <code class="language-plaintext highlighter-rouge">main</code> zwraca reprezentację strony głównej. W miejsce <code class="language-plaintext highlighter-rouge">Body</code> wstawiamy zawartość, w zależności od tego, jaki link został kliknięty lub jaki adres został podany w przeglądarce.</p>

<p>W przykładzie widzimy, że link do strony generowany jest przez member <code class="language-plaintext highlighter-rouge">router.Link</code> przyjmujący w parametrze wartość modelu <code class="language-plaintext highlighter-rouge">Page</code>. Definicja value <code class="language-plaintext highlighter-rouge">router</code> wygląda tak:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">router</span> <span class="p">=</span> <span class="nn">Router</span><span class="p">.</span><span class="n">infer</span> <span class="nc">SetPage</span> <span class="p">(</span><span class="k">fun</span> <span class="n">m</span> <span class="p">-&gt;</span> <span class="n">m</span><span class="p">.</span><span class="n">page</span><span class="p">)</span></code></pre></figure>

<p>Funkcja <code class="language-plaintext highlighter-rouge">infer</code> z modułu <code class="language-plaintext highlighter-rouge">Router</code>, należącego do <strong>Bolero</strong>, zwraca konfigurację <strong>Routing’u</strong> bazując na message, który ustawia stronę oraz na modelu reprezentującym stronę.</p>

<p>To samo value wykorzystujemy, aby poinstruować całą aplikację o zdefiniowanym <strong>Routing’u</strong>, wołając <code class="language-plaintext highlighter-rouge">Program.withRouter router</code>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">MyApp</span><span class="bp">()</span> <span class="p">=</span>
    <span class="k">inherit</span> <span class="nc">ProgramComponent</span><span class="p">&lt;</span><span class="nc">Model</span><span class="p">,</span> <span class="nc">Message</span><span class="o">&gt;()</span>

    <span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">Program</span> <span class="p">=</span>
        <span class="nn">Program</span><span class="p">.</span><span class="n">mkSimple</span> <span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">initModel</span><span class="p">)</span> <span class="n">update</span> <span class="n">view</span>
        <span class="p">|&gt;</span> <span class="nn">Program</span><span class="p">.</span><span class="n">withRouter</span> <span class="n">router</span></code></pre></figure>

<p>W taki sposób można wykorzystać <strong>Bolero Routing</strong> do symulowania funkcjonalności przechodzenia pomiędzy stronami systemu webowego.</p>

<p>Mechanizm <strong>Routing’u</strong> zawiera właściwość <strong>Page Models</strong>, która pozwala zamodelować stan dla konkretnej strony. Zajmiemy się tym tematem w następnym artykule.</p>

<p>=</p>

<h3> </h3>

<p><strong>Anonymous</strong></p>

<p>Great post. Thank you :)</p>

<p>2021-05-06 00:20 UTC</p>

<hr />
<p />

<p><a href="https://ddtd.pl">mikedevbo</a></p>

<p>Thank you very much :)</p>

<p>2021-05-06 03:54 UTC</p>]]></content><author><name>mikedevbo</name></author><summary type="html"><![CDATA[Wstęp do F# Bolero. W tym artykule przeczytasz, w jaki sposób używać mechanizmu o nazwie Routing do symulowania strony, której adres widać w pasku przeglądarki internetowej.]]></summary></entry><entry><title type="html">F# Bolero - A cóż to takiego? - view components</title><link href="/2021/02/28/fsharp-bolero-view-components.html" rel="alternate" type="text/html" title="F# Bolero - A cóż to takiego? - view components" /><published>2021-02-28T00:00:00+00:00</published><updated>2021-02-28T00:00:00+00:00</updated><id>/2021/02/28/fsharp-bolero-view-components</id><content type="html" xml:base="/2021/02/28/fsharp-bolero-view-components.html"><![CDATA[<p>Posty z tej serii:</p>

<ul>
  <li><a href="/2021/01/23/fsharp-bolero-introduction.html">F# Bolero - A cóż to takiego? - wprowadzenie</a></li>
  <li><a href="/2021/02/07/fsharp-bolero-html-templates.html">F# Bolero - A cóż to takiego? - HTML templates</a></li>
  <li><a href="/2021/02/28/fsharp-bolero-view-components.html">F# Bolero - A cóż to takiego? - view components</a></li>
  <li><a href="/2021/04/11/fsharp-bolero-routing.html">F# Bolero - A cóż to takiego? - routing</a></li>
  <li><a href="/2021/06/12/fsharp-bolero-routing-page-models.html">F# Bolero - A cóż to takiego? - routing - page models</a></li>
  <li><a href="/2021/08/18/fsharp-bolero-remoting.html">F# Bolero - A cóż to takiego? - remoting</a></li>
</ul>

<p>W procesie rozwijania oprogramowania czasami trzeba zatrzymać dorabianie nowych funkcjonalności, ponieważ istotne stają się kwestie wydajnościowe. System działał w akceptowalny sposób i nagle zaczął działać wolniej. W takim przypadku poszukiwane są miejsca, które sprawiają problemy. Zdarza się, że rozwiązanie dostarcza technologia, której używamy.</p>

<p><a href="https://fsbolero.io" title="F# Bolero">Bolero</a> posiada mechanizm <a href="https://fsbolero.io/docs/Elmish#view-components" title="View Components">View Components</a> pozwalający optymalizować renderowanie widoku. Przy każdej aktualizacji <strong>Bolero</strong> porównuje cały widok i odświeża zmienione elementy. Porównywane są również te elementy, które nie uległy zmianie. Zajmuje to pewną jednostkę obliczeniową. Mechanizm <strong>View Components</strong> pozwala definiować, kiedy poszczególne elementy mają być porównane i odświeżone. W ten sposób skracany jest czas renderowania całego widoku.</p>

<p>Zobaczmy, jak to działa na przykładzie. Tym razem zacznijmy od efektu końcowego:</p>

<p><img src="/assets/fsharp_bolero/bolero_viewcomponents.png" alt="Picture1" /></p>

<p>Pierwszy trzy elementy <strong>Nickname</strong>, <strong>Website</strong> oraz <strong>Sport</strong> renderowane są zawsze, niezależnie od tego, w którym polu tekstowym wprowadzono zmianę. Aktualizacja nastąpiła w tej samej sekundzie i prawie w tej same milisekundzie- element <strong>Last Update</strong>.</p>

<p>Kolejne trzy elementy <strong>Nickname</strong>, <strong>Website</strong> oraz <strong>Sport</strong> zrealizowane są z użyciem <strong>View Components</strong>. Każdy z elementów renderuje się tylko wtedy, kiedy wprowadzono zmianę w przypisanym do niego polu tekstowym. Aktualizacje nastąpiły w różnych sekundach i milisekundach - element <strong>Last Update</strong>.</p>

<p>Zobaczymy, w jaki sposób można zaprogramować <strong>View Components</strong>. Cały kod znajdziesz na <a href="https://github.com/mikedevbo/fsharp-bolero-sandbox" title="F# Bolero Sandbox">GitHub’e</a>.</p>

<p>Tworzymy plik <code class="language-plaintext highlighter-rouge">vieComponents.html</code> z poniższym kodem i umieszczamy w katalogu wwwroot:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;h1&gt;</span>No Components<span class="nt">&lt;/h1&gt;</span>
${NickNameInput}
${WebsiteInput}
${SportInput}
<span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;h1&gt;</span>View Components<span class="nt">&lt;/h1&gt;</span>
${NickNameInputComp}
${WebsiteInputComp}
${SportInputComp}

<span class="nt">&lt;template</span> <span class="na">id=</span><span class="s">"InputValue"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;div&gt;</span>
        <span class="nt">&lt;label&gt;</span>${Label}: <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">bind=</span><span class="s">"${Value}"</span><span class="nt">/&gt;&lt;/label&gt;</span>
        <span class="nt">&lt;label&gt;</span>Last Update: ${ValueRenderDate}<span class="nt">&lt;/label&gt;</span>
    <span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/template&gt;</span></code></pre></figure>

<p>Wykorzystujemy poznane <a href="/2021/02/07/fsharp-bolero-html-templates.html">w poprzedniej część</a> <strong>HTML Templates</strong>.</p>

<p>Definiujemy konwencję nazewniczą wg, której wszystkie elementy z końcówką <code class="language-plaintext highlighter-rouge">Comp</code> reprezentują części związane z <strong>View Components</strong>.</p>

<p>W pliku <code class="language-plaintext highlighter-rouge">Main.fs</code> programujemy logikę działania:</p>

<p>Model:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Model</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="nc">Nickname</span><span class="p">:</span> <span class="kt">string</span>
        <span class="nc">Website</span><span class="p">:</span> <span class="kt">string</span>
        <span class="nc">Sport</span><span class="p">:</span> <span class="kt">string</span>
        <span class="nc">NicknameComp</span><span class="p">:</span> <span class="kt">string</span>
        <span class="nc">WebsiteComp</span><span class="p">:</span> <span class="kt">string</span>
        <span class="nc">SportComp</span><span class="p">:</span> <span class="kt">string</span>
    <span class="p">}</span></code></pre></figure>

<p>Wszystkie wartości typu <code class="language-plaintext highlighter-rouge">string</code>.</p>

<p>Init Model:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">initModel</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="nc">Nickname</span> <span class="p">=</span> <span class="s2">""</span>
        <span class="nc">Website</span> <span class="p">=</span> <span class="s2">""</span>
        <span class="nc">Sport</span> <span class="p">=</span> <span class="s2">""</span>
        <span class="nc">NicknameComp</span> <span class="p">=</span> <span class="s2">""</span>
        <span class="nc">WebsiteComp</span> <span class="p">=</span> <span class="s2">""</span>
        <span class="nc">SportComp</span> <span class="p">=</span> <span class="s2">""</span>
    <span class="p">}</span></code></pre></figure>

<p>Na początku nic nie jest wprowadzone/wyświetlane.</p>

<p>Message:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Message</span> <span class="p">=</span>
    <span class="p">|</span> <span class="nc">SetNickname</span> <span class="k">of</span> <span class="kt">string</span>
    <span class="p">|</span> <span class="nc">SetWebsite</span> <span class="k">of</span> <span class="kt">string</span>
    <span class="p">|</span> <span class="nc">SetSport</span> <span class="k">of</span> <span class="kt">string</span>
    <span class="p">|</span> <span class="nc">SetNicknameComp</span> <span class="k">of</span> <span class="kt">string</span>
    <span class="p">|</span> <span class="nc">SetWebsiteComp</span> <span class="k">of</span> <span class="kt">string</span>
    <span class="p">|</span> <span class="nc">SetSportComp</span> <span class="k">of</span> <span class="kt">string</span></code></pre></figure>

<p>Każdy element aktualizowany jest osobno.</p>

<p>Update:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">update</span> <span class="n">message</span> <span class="n">model</span> <span class="p">=</span>
    <span class="k">match</span> <span class="n">message</span> <span class="k">with</span>
    <span class="p">|</span> <span class="nc">SetNickname</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="p">{</span><span class="n">model</span> <span class="k">with</span> <span class="nc">Nickname</span> <span class="p">=</span> <span class="n">value</span><span class="p">}</span>
    <span class="p">|</span> <span class="nc">SetWebsite</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="p">{</span><span class="n">model</span> <span class="k">with</span> <span class="nc">Website</span> <span class="p">=</span> <span class="n">value</span><span class="p">}</span>
    <span class="p">|</span> <span class="nc">SetSport</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="p">{</span><span class="n">model</span> <span class="k">with</span> <span class="nc">Sport</span> <span class="p">=</span> <span class="n">value</span><span class="p">}</span>
    <span class="p">|</span> <span class="nc">SetNicknameComp</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="p">{</span><span class="n">model</span> <span class="k">with</span> <span class="nc">NicknameComp</span> <span class="p">=</span> <span class="n">value</span><span class="p">}</span>
    <span class="p">|</span> <span class="nc">SetWebsiteComp</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="p">{</span><span class="n">model</span> <span class="k">with</span> <span class="nc">WebsiteComp</span> <span class="p">=</span> <span class="n">value</span><span class="p">}</span>
    <span class="p">|</span> <span class="nc">SetSportComp</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="p">{</span><span class="n">model</span> <span class="k">with</span> <span class="nc">SportComp</span> <span class="p">=</span> <span class="n">value</span><span class="p">}</span></code></pre></figure>

<p>Aktualizacja poszczególnych elementów modelu.</p>

<p>Template + elementy pomocnicze:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">ViewComponents</span> <span class="p">=</span> <span class="nc">Template</span><span class="p">&lt;</span><span class="s2">"wwwroot/viewComponents.html"</span><span class="p">&gt;</span>

<span class="k">let</span> <span class="nc">NicknameLabel</span> <span class="p">=</span> <span class="s2">"Nickname"</span>
<span class="k">let</span> <span class="nc">WebsiteLabel</span> <span class="p">=</span> <span class="s2">"Website"</span>
<span class="k">let</span> <span class="nc">SportLabel</span> <span class="p">=</span> <span class="s2">"Sport"</span>

<span class="k">let</span> <span class="n">viewInput</span> <span class="p">(</span><span class="n">label</span><span class="p">:</span><span class="kt">string</span><span class="p">)</span> <span class="n">value</span> <span class="n">setValue</span> <span class="p">=</span>
    <span class="nn">ViewComponents</span><span class="p">.</span><span class="nc">InputValue</span><span class="bp">()</span>
        <span class="p">.</span><span class="nc">Label</span><span class="p">(</span><span class="n">label</span><span class="p">)</span>
        <span class="p">.</span><span class="nc">Value</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="k">fun</span> <span class="n">vp</span> <span class="p">-&gt;</span> <span class="n">setValue</span> <span class="n">vp</span><span class="p">)</span>
        <span class="p">.</span><span class="nc">ValueRenderDate</span><span class="p">(</span><span class="nn">DateTime</span><span class="p">.</span><span class="nn">Now</span><span class="p">.</span><span class="nc">ToString</span><span class="p">(</span><span class="s2">"ss.ffff"</span><span class="o">))</span>
        <span class="p">.</span><span class="nc">Elt</span><span class="bp">()</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">ViewComponents</code> reprezentuje <strong>Template</strong>. Definiujemy pomocne <strong>labels</strong> oraz funkcję <code class="language-plaintext highlighter-rouge">viewInput</code>, która renderuje poszczególne elementy widoku. Konkretne wartości przekazywane są w parametrach funkcji. Pokazywany jest również punkt w czasie (w sekundach i milisekundach), w którym nastąpiło renderowanie widoku.</p>

<p>To jest cała istota programowania funkcyjnego - dziel elementy na małe funkcje i składaj je w jedną działającą całość :)</p>

<p>Definicja <strong>View Components</strong>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">InputComponentModel</span> <span class="p">=</span> <span class="p">{</span> <span class="n">label</span><span class="p">:</span> <span class="kt">string</span><span class="p">;</span> <span class="n">value</span><span class="p">:</span> <span class="kt">string</span> <span class="p">}</span>

<span class="k">type</span> <span class="nc">InputComponent</span><span class="bp">()</span> <span class="p">=</span>
    <span class="k">inherit</span> <span class="nc">ElmishComponent</span><span class="p">&lt;</span><span class="nc">InputComponentModel</span><span class="p">,</span> <span class="kt">string</span><span class="o">&gt;()</span>

    <span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">ShouldRender</span><span class="p">(</span><span class="n">oldModel</span><span class="p">,</span> <span class="n">newModel</span><span class="p">)</span> <span class="p">=</span>
        <span class="n">oldModel</span><span class="p">.</span><span class="n">value</span> <span class="p">&lt;&gt;</span> <span class="n">newModel</span><span class="p">.</span><span class="n">value</span>

    <span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">View</span> <span class="n">model</span> <span class="n">dispatch</span> <span class="p">=</span>
        <span class="n">viewInput</span> <span class="n">model</span><span class="p">.</span><span class="n">label</span> <span class="n">model</span><span class="p">.</span><span class="n">value</span> <span class="n">dispatch</span></code></pre></figure>

<p><strong>View Component</strong> zazwyczaj przyjmuje tylko część z całego modelu. W naszym przypadku model reprezentowany jest przez <code class="language-plaintext highlighter-rouge">InputComponentModel</code>, który zawiera <strong>label</strong> oraz <strong>value</strong>.</p>

<p><strong>View Component</strong> tworzymy przez dziedziczenie po klasie <code class="language-plaintext highlighter-rouge">ElmishComponent</code> podając w parametrach generycznych <strong>model type</strong> oraz <strong>message type</strong>. W naszym przypadku są to <code class="language-plaintext highlighter-rouge">InputComponentModel</code> oraz <code class="language-plaintext highlighter-rouge">string</code>. Sam komponent nazywamy <code class="language-plaintext highlighter-rouge">InputComponent</code>.</p>

<p>W metodzie <code class="language-plaintext highlighter-rouge">ShouldRender</code> określamy, kiedy komponent powinien odświeżyć swój widok. W naszym przypadku jest to moment, kiedy następuje zmiana wartości <code class="language-plaintext highlighter-rouge">value</code>.</p>

<p>Metoda <code class="language-plaintext highlighter-rouge">View</code> odświeża widok. W naszym przypadku wykorzystujemy wcześniej utworzoną funkcję <code class="language-plaintext highlighter-rouge">viewInput</code>.</p>

<p>Renderowanie całego widoku:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">view</span> <span class="n">model</span> <span class="n">dispatch</span> <span class="p">=</span>
    <span class="nc">ViewComponents</span><span class="bp">()</span>
        <span class="p">.</span><span class="nc">NickNameInput</span><span class="p">(</span><span class="n">viewInput</span> <span class="nc">NicknameLabel</span> <span class="n">model</span><span class="p">.</span><span class="nc">Nickname</span> <span class="p">(</span><span class="k">fun</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="p">(</span><span class="nc">SetNickname</span> <span class="n">value</span><span class="o">)))</span>
        <span class="p">.</span><span class="nc">WebsiteInput</span><span class="p">(</span><span class="n">viewInput</span> <span class="nc">WebsiteLabel</span> <span class="n">model</span><span class="p">.</span><span class="nc">Website</span> <span class="p">(</span><span class="k">fun</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="p">(</span><span class="nc">SetWebsite</span> <span class="n">value</span><span class="o">)))</span>
        <span class="p">.</span><span class="nc">SportInput</span><span class="p">(</span><span class="n">viewInput</span> <span class="nc">SportLabel</span> <span class="n">model</span><span class="p">.</span><span class="nc">Sport</span> <span class="p">(</span><span class="k">fun</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="p">(</span><span class="nc">SetSport</span> <span class="n">value</span><span class="o">)))</span>
        <span class="p">.</span><span class="nc">NickNameInputComp</span><span class="p">(</span><span class="n">ecomp</span><span class="p">&lt;</span><span class="nc">InputComponent</span><span class="p">,</span> <span class="o">_,</span> <span class="o">_&gt;</span> <span class="bp">[]</span> <span class="p">{</span><span class="n">label</span> <span class="p">=</span> <span class="nc">NicknameLabel</span><span class="p">;</span> <span class="n">value</span> <span class="p">=</span> <span class="n">model</span><span class="p">.</span><span class="nc">NicknameComp</span><span class="p">}</span> <span class="p">(</span><span class="k">fun</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="p">(</span><span class="nc">SetNicknameComp</span> <span class="n">value</span><span class="o">)))</span>
        <span class="p">.</span><span class="nc">WebsiteInputComp</span><span class="p">(</span><span class="n">ecomp</span><span class="p">&lt;</span><span class="nc">InputComponent</span><span class="p">,</span> <span class="o">_,</span> <span class="o">_&gt;</span> <span class="bp">[]</span> <span class="p">{</span><span class="n">label</span> <span class="p">=</span> <span class="nc">WebsiteLabel</span><span class="p">;</span> <span class="n">value</span> <span class="p">=</span> <span class="n">model</span><span class="p">.</span><span class="nc">WebsiteComp</span><span class="p">}</span> <span class="p">(</span><span class="k">fun</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="p">(</span><span class="nc">SetWebsiteComp</span> <span class="n">value</span><span class="o">)))</span>
        <span class="p">.</span><span class="nc">SportInputComp</span><span class="p">(</span><span class="n">ecomp</span><span class="p">&lt;</span><span class="nc">InputComponent</span><span class="p">,</span> <span class="o">_,</span> <span class="o">_&gt;</span> <span class="bp">[]</span> <span class="p">{</span><span class="n">label</span> <span class="p">=</span> <span class="nc">SportLabel</span><span class="p">;</span> <span class="n">value</span> <span class="p">=</span> <span class="n">model</span><span class="p">.</span><span class="nc">SportComp</span><span class="p">}</span> <span class="p">(</span><span class="k">fun</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="p">(</span><span class="nc">SetSportComp</span> <span class="n">value</span><span class="o">)))</span>
        <span class="p">.</span><span class="nc">Elt</span><span class="bp">()</span></code></pre></figure>

<p>Instancję <code class="language-plaintext highlighter-rouge">InputComponent</code> tworzymy, wykorzystując, wbudowaną w <strong>Bolero</strong>, funkcję <code class="language-plaintext highlighter-rouge">ecomp</code>. W parametrach generycznych podajemy typ komponentu, a w parametrach funkcji model oraz funkcję <code class="language-plaintext highlighter-rouge">dispatch</code>, która zostanie wywołana w momencie wykonania akcji na komponencie.</p>

<p>W ten sposób możemy decydować, czy dany element widoku powinien być brany pod uwagę w momencie renderowania całości.</p>

<p>To tyle, jeśli chodzi o programowanie w <strong>Bolero</strong> z użyciem <strong>View Components</strong>. W następnym artykule sprawdzimy, jak działa mechanizm o nazwie <strong>Routing</strong>.</p>

<p>=</p>

<h3> </h3>]]></content><author><name>mikedevbo</name></author><summary type="html"><![CDATA[Wstęp do F# Bolero. W tym artykule przeczytasz, w jaki sposób używać View Components do optymalizacji renderowania widoku.]]></summary></entry><entry><title type="html">F# Bolero - A cóż to takiego? - HTML templates</title><link href="/2021/02/07/fsharp-bolero-html-templates.html" rel="alternate" type="text/html" title="F# Bolero - A cóż to takiego? - HTML templates" /><published>2021-02-07T00:00:00+00:00</published><updated>2021-02-07T00:00:00+00:00</updated><id>/2021/02/07/fsharp-bolero-html-templates</id><content type="html" xml:base="/2021/02/07/fsharp-bolero-html-templates.html"><![CDATA[<p>Posty z tej serii:</p>

<ul>
  <li><a href="/2021/01/23/fsharp-bolero-introduction.html">F# Bolero - A cóż to takiego? - wprowadzenie</a></li>
  <li><a href="/2021/02/07/fsharp-bolero-html-templates.html">F# Bolero - A cóż to takiego? - HTML templates</a></li>
  <li><a href="/2021/02/28/fsharp-bolero-view-components.html">F# Bolero - A cóż to takiego? - view components</a></li>
  <li><a href="/2021/04/11/fsharp-bolero-routing.html">F# Bolero - A cóż to takiego? - routing</a></li>
  <li><a href="/2021/06/12/fsharp-bolero-routing-page-models.html">F# Bolero - A cóż to takiego? - routing - page models</a></li>
  <li><a href="/2021/08/18/fsharp-bolero-remoting.html">F# Bolero - A cóż to takiego? - remoting</a></li>
</ul>

<p>Konstruowanie elementów UI <a href="https://fsbolero.io/docs/HTML" title="F# Bolero HTML">w kodzie F#</a>. Czy jest to dobre podejście? Kiedy pierwszy raz zobaczyłem taką możliwość, pomyślałem - <em>ciekawe, ale nie jestem do końca przekonany</em>. Z jednej strony mamy wsparcie kompilatora, który pilnuje poprawności wyniku. Z drugiej strony, każda, nawet najmniejsza zmiana wymaga re-kompilacji całości. Z trzeciej strony, dla osób, które piszą <strong>backend</strong>, re-kompilacja po zmianie to norma. Z czwartej strony, nie da się podzielić pracy tak, aby projektowaniem UI oraz przygotowaniem prototypu zajmowały się osoby od projektowania, a programowaniem całości zajmowały się osoby od programowania. Wszystko jest w kodzie <strong>F#</strong>, więc wszystko trzeba zaprogramować. Na chwilę obecną nadal podchodzę z pewną rezerwą do takiego podejścia, ale nie skreślam całkowicie.</p>

<p><strong>Bolero</strong> umożliwia także alternatywny sposób konstruowania elementów UI - za pomocą tzw. <a href="https://fsbolero.io/docs/Templating" title="F# Bolero Templates">HTML Templates</a>. Do realizacji używamy standardowych znaczników <strong>HTML</strong>, w których możemy zawrzeć tzw. <strong>Holes</strong>. Służą one jako łącznik pomiędzy kodem UI napisanym w <strong>HTML</strong>, a logiką napisaną w <strong>F#</strong>. Dzięki temu możemy wyświetlać dynamiczne elementy na stronie oraz pobierać informacje, wprowadzane w formularzach. Nazywa się to <strong>bindowaniem</strong> danych. Możemy również wydzielać powtarzalne części za pomocą tzw. <strong>Nested Templates</strong>.</p>

<p>Zobaczmy, na przykładach, jak to wszystko działa.</p>

<p>Całą implementację znajdziesz na <a href="https://github.com/mikedevbo/fsharp-bolero-sandbox" title="F# Bolero Sandbox">GitHub’e</a>.</p>

<h2 id="simple-counter">Simple Counter</h2>

<p>W <a href="/2021/01/23/fsharp-bolero-introduction.html">poprzedniej części</a> stworzyliśmy prosty licznik, przesuwający wartość w dwie strony. Zamienimy kod renderujący widok tak, aby używał <strong>HTML Templates</strong>.</p>

<p>Tworzymy plik <code class="language-plaintext highlighter-rouge">counter.html</code> z poniższym kodem i umieszczamy w katalogu <code class="language-plaintext highlighter-rouge">wwwroot</code>:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div&gt;</span>
    <span class="nt">&lt;button</span> <span class="na">onclick=</span><span class="s">"${Decrement}"</span><span class="nt">&gt;</span>-<span class="nt">&lt;/button&gt;</span>
    <span class="nt">&lt;span&gt;</span>${Value}<span class="nt">&lt;/span&gt;</span>
    <span class="nt">&lt;button</span> <span class="na">onclick=</span><span class="s">"${Increment}"</span><span class="nt">&gt;</span>+<span class="nt">&lt;/button&gt;</span>
<span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;div&gt;</span>
    <span class="nt">&lt;button</span> <span class="na">onclick=</span><span class="s">"${Reset}"</span><span class="nt">&gt;</span>Reset<span class="nt">&lt;/button&gt;</span>
<span class="nt">&lt;/div&gt;</span></code></pre></figure>

<p>Między znacznikami <strong>HTML</strong> znajdują się <strong>Holes</strong> <code class="language-plaintext highlighter-rouge">${Decrement}</code>, <code class="language-plaintext highlighter-rouge">${Value}</code>, <code class="language-plaintext highlighter-rouge">${Increment}</code> oraz <code class="language-plaintext highlighter-rouge">${Reset}</code> pod które podstawimy wartości w kodzie <strong>F#</strong>.</p>

<p>W pliku <code class="language-plaintext highlighter-rouge">Main.fs</code> definiujemy typ, który będzie reprezentował <strong>Template</strong>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Counter</span> <span class="p">=</span> <span class="nc">Template</span><span class="p">&lt;</span><span class="s2">"wwwroot/counter.html"</span><span class="p">&gt;</span></code></pre></figure>

<p>Zamieniamy definicję funkcji <code class="language-plaintext highlighter-rouge">view</code> z:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">view</span> <span class="n">model</span> <span class="n">dispatch</span> <span class="p">=</span>
    <span class="n">concat</span> <span class="p">[</span>
        <span class="n">div</span> <span class="bp">[]</span> <span class="p">[</span>
            <span class="n">button</span> <span class="p">[</span><span class="n">on</span><span class="p">.</span><span class="n">click</span> <span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">Decrement</span><span class="o">)]</span> <span class="p">[</span><span class="n">text</span> <span class="s2">"-"</span><span class="p">]</span>
            <span class="n">text</span> <span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>
            <span class="n">button</span> <span class="p">[</span><span class="n">on</span><span class="p">.</span><span class="n">click</span> <span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">Increment</span><span class="o">)]</span> <span class="p">[</span><span class="n">text</span> <span class="s2">"+"</span><span class="p">]</span>
        <span class="p">]</span>

        <span class="n">div</span> <span class="bp">[]</span> <span class="p">[</span>
            <span class="n">button</span> <span class="p">[</span><span class="n">on</span><span class="p">.</span><span class="n">click</span> <span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">Reset</span><span class="o">)]</span> <span class="p">[</span><span class="n">text</span> <span class="s2">"Reset"</span><span class="p">]</span>
        <span class="p">]</span>
    <span class="p">]</span></code></pre></figure>

<p>na:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">view</span> <span class="n">model</span> <span class="n">dispatch</span> <span class="p">=</span>
    <span class="nc">Counter</span><span class="bp">()</span>
        <span class="p">.</span><span class="nc">Decrement</span><span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">Decrement</span><span class="p">)</span>
        <span class="p">.</span><span class="nc">Value</span><span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>
        <span class="p">.</span><span class="nc">Increment</span><span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">Increment</span><span class="p">)</span>
        <span class="p">.</span><span class="nc">Reset</span><span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">Reset</span><span class="p">)</span>
        <span class="p">.</span><span class="nc">Elt</span><span class="bp">()</span></code></pre></figure>

<p>Konstruktor <code class="language-plaintext highlighter-rouge">Counter()</code> inicjalizuje <strong>Template</strong>. <strong>Bolero</strong> rozpoznaje <strong>Holes</strong> zdefiniowane w pliku <code class="language-plaintext highlighter-rouge">counter.html</code>. Dla każdego tworzy odpowiednik w postaci metody z taką samą nazwą oraz pasującą sygnaturą. W ten sposób możemy zakodować zachowanie programu:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Decrement</code>, <code class="language-plaintext highlighter-rouge">Increment</code> oraz <code class="language-plaintext highlighter-rouge">Reset</code> reprezentują kliknięcie odpowiedniego przycisku na stronie</li>
  <li><code class="language-plaintext highlighter-rouge">Value</code> reprezentuje wyświetlaną wartość licznika</li>
  <li><code class="language-plaintext highlighter-rouge">Elt</code> jest wewnętrzną metodą <strong>Bolero</strong>, która konwertuje całą definicję do końcowego typu</li>
</ul>

<p>Wszystko jest silnie typowane. Mamy pełne wsparcie kompilatora oraz podpowiadanie składni. W <a href="https://www.jetbrains.com/rider/" title="Jetbrains Rider">JetBrains Rider</a> wygląda to tak:</p>

<p><img src="/assets/fsharp_bolero/bolero_temp_code_comp.png" alt="Picture1" /></p>

<p>Stan licznika wyświetlamy, wołając metodę <code class="language-plaintext highlighter-rouge">Value</code>. Do parametru metody przekazujemy wartość <code class="language-plaintext highlighter-rouge">model.value</code>, skonwertowaną z typu <code class="language-plaintext highlighter-rouge">int</code> - taki przechowujemy w modelu, na typ <code class="language-plaintext highlighter-rouge">string</code> - takiego spodziewa się metoda. Służy do tego operator o nazwie <code class="language-plaintext highlighter-rouge">string</code>.</p>

<p>Zachowanie kodujemy, wykorzystując funkcję <code class="language-plaintext highlighter-rouge">dispatch</code>, której podajemy odpowiedni <code class="language-plaintext highlighter-rouge">Message</code> reprezentujący konkretną akcję.</p>

<p>A jak działa <strong>bindowanie</strong>? Sprawdźmy to, dodając do licznika funkcjonalność ustawiania liczby, która będzie wskazywać, o ile licznik ma się przesuwać.</p>

<p>Rozszerzamy <code class="language-plaintext highlighter-rouge">counter.html</code> o możliwość wprowadzenia wartości:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div&gt;</span>
    <span class="nt">&lt;label&gt;</span>
        Step: <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span>  <span class="na">bind=</span><span class="s">"${Step}"</span> <span class="na">onkeydown=</span><span class="s">"${KeyDown}"</span> <span class="nt">/&gt;</span>
        <span class="nt">&lt;span&gt;</span>Key: ${Key} - Key Code: ${KeyCode}<span class="nt">&lt;/span&gt;</span>
    <span class="nt">&lt;/label&gt;</span>
<span class="nt">&lt;/div&gt;</span>
<span class="c">&lt;!-- whole code you can see on GitHub
// ... --&gt;</span></code></pre></figure>

<p>Atrybut <code class="language-plaintext highlighter-rouge">bind</code> łączy wartość znacznika <code class="language-plaintext highlighter-rouge">input</code> z <strong>Hole</strong> <code class="language-plaintext highlighter-rouge">${Step}</code>. Przykład zawiera dodatkową funkcjonalność - wyświetlenie informacji o naciśniętym klawiszu - <strong>Holes</strong> <code class="language-plaintext highlighter-rouge">${KeyDown}</code>, <code class="language-plaintext highlighter-rouge">${Key}</code> oraz <code class="language-plaintext highlighter-rouge">${KeyCode}</code></p>

<p>Rozszerzamy model dodając elementy <code class="language-plaintext highlighter-rouge">step</code>, <code class="language-plaintext highlighter-rouge">key</code> oraz <code class="language-plaintext highlighter-rouge">keyCode</code>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Model</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="c1">// whole code you can see on GitHub</span>
        <span class="c1">// ...</span>
        <span class="n">step</span><span class="p">:</span> <span class="kt">int</span>
        <span class="n">key</span><span class="p">:</span> <span class="kt">string</span>
        <span class="n">keyCode</span><span class="p">:</span> <span class="kt">string</span>        
    <span class="p">}</span></code></pre></figure>

<p>Ustawiamy wartości początkowe <code class="language-plaintext highlighter-rouge">initModel</code>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">initModel</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="c1">// whole code you can see on GitHub</span>
        <span class="c1">// ...</span>
        <span class="n">step</span> <span class="p">=</span> <span class="mi">1</span>
        <span class="n">key</span> <span class="p">=</span> <span class="s2">""</span>
        <span class="n">keyCode</span> <span class="p">=</span> <span class="s2">""</span>
    <span class="p">}</span></code></pre></figure>

<p>Dodajemy <strong>Message’e</strong>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Message</span> <span class="p">=</span>
    <span class="c1">// whole code you can see on GitHub</span>
    <span class="c1">// ...</span>
    <span class="p">|</span> <span class="nc">SetStep</span> <span class="k">of</span> <span class="kt">int</span>
    <span class="p">|</span> <span class="nc">KeyDown</span> <span class="k">of</span> <span class="kt">string</span> <span class="p">*</span> <span class="kt">string</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">SetStep</code> - wysyłany w momencie wprowadzania nowej wartości przesunięcia licznika - wartość podawana w parametrze typu <code class="language-plaintext highlighter-rouge">int</code></p>

<p><code class="language-plaintext highlighter-rouge">KeyDown</code> - wysyłany w momencie naciśnięcia klawisza na klawiaturze - klawisz oraz jego kod podawane w parametrze typu <code class="language-plaintext highlighter-rouge">string * string</code> - <strong>Tuple</strong></p>

<p>Aktualizujemy funkcję <code class="language-plaintext highlighter-rouge">update</code>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">update</span> <span class="n">message</span> <span class="n">model</span> <span class="p">=</span>
    <span class="k">match</span> <span class="n">message</span> <span class="k">with</span>
    <span class="p">|</span> <span class="nc">Increment</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">value</span> <span class="p">=</span> <span class="n">model</span><span class="p">.</span><span class="n">value</span> <span class="o">+</span> <span class="n">model</span><span class="p">.</span><span class="n">step</span> <span class="p">}</span>
    <span class="p">|</span> <span class="nc">Decrement</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">value</span> <span class="p">=</span> <span class="n">model</span><span class="p">.</span><span class="n">value</span> <span class="p">-</span> <span class="n">model</span><span class="p">.</span><span class="n">step</span> <span class="p">}</span>
    <span class="p">|</span> <span class="nc">Reset</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">value</span> <span class="p">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">step</span> <span class="p">=</span> <span class="mi">1</span> <span class="p">}</span>
    <span class="p">|</span> <span class="nc">SetStep</span> <span class="n">step</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">step</span> <span class="p">=</span> <span class="n">step</span> <span class="p">}</span>
    <span class="p">|</span> <span class="nc">KeyDown</span> <span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">keyCode</span><span class="p">)</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">key</span> <span class="p">=</span> <span class="n">key</span><span class="p">;</span> <span class="n">keyCode</span> <span class="p">=</span> <span class="n">keyCode</span> <span class="p">}</span></code></pre></figure>

<p>Licznik przesuwany jest o wartość, zapamiętaną w <code class="language-plaintext highlighter-rouge">model.step</code>. <code class="language-plaintext highlighter-rouge">Reset</code> ustawia <code class="language-plaintext highlighter-rouge">step</code> na wartość początkową. <code class="language-plaintext highlighter-rouge">SetStep</code> ustawia wartość przesunięcia, a <code class="language-plaintext highlighter-rouge">KeyDown</code> informacje o naciśniętym klawiszu.</p>

<p>Rozszerzamy funkcję <code class="language-plaintext highlighter-rouge">view</code>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">view</span> <span class="n">model</span> <span class="n">dispatch</span> <span class="p">=</span>
    <span class="c1">// whole code you can see on GitHub</span>
    <span class="c1">// ...</span>
    <span class="p">.</span><span class="nc">Step</span><span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">.</span><span class="n">step</span><span class="p">,</span> <span class="k">fun</span> <span class="n">inputStep</span> <span class="p">-&gt;</span>
        <span class="k">match</span> <span class="nn">System</span><span class="p">.</span><span class="nn">Int32</span><span class="p">.</span><span class="nc">TryParse</span> <span class="n">inputStep</span> <span class="k">with</span>
        <span class="p">|</span> <span class="bp">true</span><span class="p">,</span> <span class="n">step</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="p">(</span><span class="nc">SetStep</span> <span class="p">(</span><span class="n">step</span><span class="o">))</span>
        <span class="p">|</span> <span class="bp">false</span><span class="p">,</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="bp">()</span><span class="p">)</span>
    <span class="p">.</span><span class="nc">Key</span><span class="p">(</span><span class="n">model</span><span class="p">.</span><span class="n">key</span><span class="p">)</span>
    <span class="p">.</span><span class="nc">KeyCode</span><span class="p">(</span><span class="n">model</span><span class="p">.</span><span class="n">keyCode</span><span class="p">)</span>
    <span class="p">.</span><span class="nc">KeyDown</span><span class="p">(</span><span class="k">fun</span> <span class="n">key</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="p">(</span><span class="nc">KeyDown</span> <span class="p">(</span><span class="n">key</span><span class="p">.</span><span class="nc">Key</span><span class="p">,</span> <span class="n">key</span><span class="p">.</span><span class="nc">Code</span><span class="o">)))</span>
    <span class="p">.</span><span class="nc">Elt</span><span class="bp">()</span></code></pre></figure>

<p>Metoda <code class="language-plaintext highlighter-rouge">Step</code> wyśle <code class="language-plaintext highlighter-rouge">Message</code> aktualizujący wartość przesunięcia tylko wtedy, kiedy wprowadzony napis jest liczbą. W tym miejscu powinna być <strong>pełna walidacja</strong>, ale dla uproszczenia pomijamy ten element. Metody <code class="language-plaintext highlighter-rouge">Key</code> oraz <code class="language-plaintext highlighter-rouge">KeyCode</code> wyświetlają informacje o klawiszu. Metoda <code class="language-plaintext highlighter-rouge">KeyDown</code> wysyła <code class="language-plaintext highlighter-rouge">Message</code> aktualizujący informacje o naciśniętym klawiszu. Tak jak w poprzednim przykładzie, tak i tu, wszystko jest silnie typowane i wspierane przez kompilator <strong>F#</strong>.</p>

<p>Ostatecznie otrzymujemy program:</p>

<p><img src="/assets/fsharp_bolero/bolero_counter_templates.png" alt="Picture2" /></p>

<p>Zobaczmy kolejny przykład.</p>

<h2 id="enter-values">Enter Values</h2>

<p>W sytuacji, kiedy pewne elementy zawierają tę samą strukturę kodu <strong>HTML</strong>, ale różnią się wartościami, warto stworzyć dla nich re-używalny komponent. W <strong>Bolero</strong> możemy to zrobić, używając <strong>Nested Templates</strong>. Zobaczmy, jak to działa na przykładzie programu wyświetlającego wprowadzane napisy.</p>

<p>Definiujemy <strong>Template</strong>:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;div&gt;</span>
    <span class="nt">&lt;label&gt;</span>Enter value and press ENTER: <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">bind-onchange=</span><span class="s">"${NewValue}"</span><span class="nt">/&gt;&lt;/label&gt;</span>
    <span class="nt">&lt;div&gt;&lt;span&gt;</span>Entered Values:<span class="nt">&lt;/span&gt;&lt;/div&gt;</span>
    ${EnteredValues}
    <span class="nt">&lt;div&gt;&lt;button</span> <span class="na">onclick=</span><span class="s">"${ClearValues}"</span><span class="nt">&gt;</span>Clear<span class="nt">&lt;/button&gt;&lt;/div&gt;</span>
<span class="nt">&lt;/div&gt;</span>

<span class="nt">&lt;template</span> <span class="na">id=</span><span class="s">"ShowValue"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;div&gt;</span>
        <span class="nt">&lt;span&gt;</span>${Value}<span class="nt">&lt;/span&gt;</span>
    <span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/template&gt;</span></code></pre></figure>

<p>Zasada działania jest taka sama jak w przykładzie z licznikiem - <strong>HTML</strong> + <strong>Holes</strong>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">${NewValue}</code> - reprezentuje wprowadzaną wartość</li>
  <li><code class="language-plaintext highlighter-rouge">${EnteredValues}</code> - reprezentuje listę wprowadzonych wartości</li>
  <li><code class="language-plaintext highlighter-rouge">${ClearValues}</code> - reprezentuje naciśnięcie przycisku czyszczącego wprowadzone wartości</li>
</ul>

<p>Nowością jest <code class="language-plaintext highlighter-rouge">&lt;template id="ShowValue"&gt;</code> - definicja <strong>Nested Template</strong>. Template nie jest wyświetlony na ekranie, można go użyć w kodzie <strong>F#</strong>. <strong>Hole</strong> <code class="language-plaintext highlighter-rouge">${Value}</code> reprezentuje pojedynczą wyświetlaną wartość.</p>

<p>Druga nowość to zastąpienie atrybutu <code class="language-plaintext highlighter-rouge">bind</code> przez <code class="language-plaintext highlighter-rouge">bind-onchange</code>. Dzięki temu <code class="language-plaintext highlighter-rouge">Message</code> wysyłany jest po naciśnięciu klawisza <code class="language-plaintext highlighter-rouge">ENTER</code>, a nie po każdorazowym wprowadzeniu znaku.</p>

<p>Pozostaje zakodować logikę w <strong>F#</strong> wg standardowego schematu.</p>

<p>Model:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Model</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">enteredValues</span><span class="p">:</span> <span class="kt">string</span> <span class="kt">list</span>
    <span class="p">}</span></code></pre></figure>

<p>Wartości trzymamy jako listę napisów.</p>

<p>Init:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">initModel</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">enteredValues</span> <span class="p">=</span> <span class="bp">[]</span>
    <span class="p">}</span></code></pre></figure>

<p>Message:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Message</span> <span class="p">=</span>
    <span class="p">|</span> <span class="nc">NewValue</span> <span class="k">of</span> <span class="kt">string</span>
    <span class="p">|</span> <span class="nc">ClearValues</span></code></pre></figure>

<p>Zgłaszamy wprowadzenie nowej wartości lub wyczyszczenie wprowadzonych danych.</p>

<p>Template:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">EnterValuesTemplate</span> <span class="p">=</span> <span class="nc">Template</span><span class="p">&lt;</span><span class="s2">"wwwroot/enterValues.html"</span><span class="p">&gt;</span></code></pre></figure>

<p>Ładowany z pliku <code class="language-plaintext highlighter-rouge">enterValues.html</code></p>

<p>Update:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">update</span> <span class="n">message</span> <span class="n">model</span> <span class="p">=</span>
    <span class="k">match</span> <span class="n">message</span> <span class="k">with</span>
    <span class="p">|</span> <span class="nc">NewValue</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">enteredValues</span> <span class="p">=</span> <span class="n">model</span><span class="p">.</span><span class="n">enteredValues</span> <span class="o">@</span> <span class="p">[</span><span class="n">value</span><span class="p">]</span> <span class="p">}</span>
    <span class="p">|</span> <span class="nc">ClearValues</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">enteredValues</span> <span class="p">=</span> <span class="bp">[]</span> <span class="p">}</span></code></pre></figure>

<p>Operator <code class="language-plaintext highlighter-rouge">@</code> pozwala połączyć dwie listy w jedną.</p>

<p>View:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">view</span> <span class="n">model</span> <span class="n">dispatch</span> <span class="p">=</span>
    <span class="nc">EnterValuesTemplate</span><span class="bp">()</span>
        <span class="p">.</span><span class="nc">EnteredValues</span><span class="p">(</span><span class="n">forEach</span> <span class="n">model</span><span class="p">.</span><span class="n">enteredValues</span> <span class="p">(</span><span class="k">fun</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="nn">EnterValuesTemplate</span><span class="p">.</span><span class="nc">ShowValue</span><span class="bp">()</span><span class="p">.</span><span class="nc">Value</span><span class="p">(</span><span class="n">value</span><span class="o">).</span><span class="nc">Elt</span><span class="bp">()</span><span class="o">))</span>
        <span class="p">.</span><span class="nc">NewValue</span><span class="p">(</span><span class="s2">""</span><span class="p">,</span> <span class="k">fun</span> <span class="n">value</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="p">(</span><span class="nc">NewValue</span> <span class="n">value</span><span class="o">))</span>
        <span class="p">.</span><span class="nc">ClearValues</span><span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">ClearValues</span><span class="p">)</span>
        <span class="p">.</span><span class="nc">Elt</span><span class="bp">()</span></code></pre></figure>

<p>Do <strong>Nested Template</strong> odwołujemy się po identyfikatorze zdefiniowanym w pliku <strong>.html</strong>, wg schematu - <code class="language-plaintext highlighter-rouge">Template.NestedTemplate()</code>. W przykładzie jest to <code class="language-plaintext highlighter-rouge">EnterValuesTemplate.ShowValue()</code>. 
Wartości z listy, podajemy jako parametr do metody <code class="language-plaintext highlighter-rouge">EnteredValues</code>. Każdy element wyświetlany jest jako osobny <strong>Nested Template</strong>.</p>

<p>Ostatecznie otrzymujemy program:</p>

<p><img src="/assets/fsharp_bolero/bolero_enter_values.png" alt="Picture3" /></p>

<h2 id="hot-reloading">Hot Reloading</h2>

<p>Wspólną cechą framework’ów webowych generujących wynik po stronie klienta, jest automatyczne odświeżanie widoku, w momencie zapisu zmienionego kodu. Nazywa się to <strong>Hot Reloading</strong>. <strong>Bolero</strong> również posiada taką możliwości. Po zapisaniu zmian w pliku <strong>*.html</strong>, następuje przeładowanie i wynik dostępny jest na stronie. Oczywiście, jeśli zmiany wprowadzamy w kodzie <strong>F#</strong>, to dalej trzeba dokonać kompilacji.</p>

<p>Dla mnie, jako <strong>backend’owca</strong>, kompilacja kodu po zmianach to standardowa, wręcz naturalna rzecz. Obecnie, gdy działam z <strong>Bolero</strong> nie używam mechanizmu <strong>Hot Reloading</strong>. Być może to się zmieni, kiedy będę pisał więcej kodu UI, zwłaszcza na etapie prototypowania interfejsów.</p>

<p>Generowany kod za pomocą <code class="language-plaintext highlighter-rouge">dotnet new bolero-app -o MyApp</code> zawiera konfigurację <strong>Hot Reloading</strong>. Szczegóły, jak włączyć tę funkcjonalność we własnym projekcie znajdują się <a href="https://fsbolero.io/docs/Templating#hot-reloading" title="F# Bolero Hot Reloading">na stronie dokumentacji</a>.</p>

<p>Tak wygląda programowanie w <strong>Bolero</strong> z użyciem <strong>HTML Templates</strong>. W następnym artykule zajmiemy się mechanizmem <strong>View Components</strong>.</p>

<p>=</p>

<h3> </h3>]]></content><author><name>mikedevbo</name></author><summary type="html"><![CDATA[Wstęp do F# Bolero. W tym artykule przeczytasz, w jaki sposób używać HTML Templates do konstruowania UI.]]></summary></entry><entry><title type="html">F# Bolero - A cóż to takiego? - wprowadzenie</title><link href="/2021/01/23/fsharp-bolero-introduction.html" rel="alternate" type="text/html" title="F# Bolero - A cóż to takiego? - wprowadzenie" /><published>2021-01-23T00:00:00+00:00</published><updated>2021-01-23T00:00:00+00:00</updated><id>/2021/01/23/fsharp-bolero-introduction</id><content type="html" xml:base="/2021/01/23/fsharp-bolero-introduction.html"><![CDATA[<p>Posty z tej serii:</p>

<ul>
  <li><a href="/2021/01/23/fsharp-bolero-introduction.html">F# Bolero - A cóż to takiego? - wprowadzenie</a></li>
  <li><a href="/2021/02/07/fsharp-bolero-html-templates.html">F# Bolero - A cóż to takiego? - HTML templates</a></li>
  <li><a href="/2021/02/28/fsharp-bolero-view-components.html">F# Bolero - A cóż to takiego? - view components</a></li>
  <li><a href="/2021/04/11/fsharp-bolero-routing.html">F# Bolero - A cóż to takiego? - routing</a></li>
  <li><a href="/2021/06/12/fsharp-bolero-routing-page-models.html">F# Bolero - A cóż to takiego? - routing - page models</a></li>
  <li><a href="/2021/08/18/fsharp-bolero-remoting.html">F# Bolero - A cóż to takiego? - remoting</a></li>
</ul>

<p>Programowanie Webowe. Sposób wytwarzania oprogramowania, dzięki któremu produkt może zostać uruchomiony w przeglądarce internetowej. Zagadnienie jest bardzo obszerne. Możliwości realizacji można dzielić na różne kategorie, np. ze względu na <strong>miejsce</strong> generowania wyniku:</p>

<ul>
  <li>Server Side - generowanie zawartości po stronie serwera, a następnie przesłanie do klienta - przeglądarki internetowej</li>
  <li>Client Side - generowanie zawartości po stronie klienta - przeglądarce internetowej</li>
</ul>

<p>Inny podział to <strong>sposób</strong> generowania wyniku:</p>

<ul>
  <li>Statyczny - pobieranie gotowej, wcześniej przygotowanej zawartości</li>
  <li>Dynamiczny - generowanie zawartości na żądanie, w momencie jej pobierania</li>
</ul>

<p>Kategorie można łączyć ze sobą:</p>

<ul>
  <li>Server Side / Statyczny - pierwsze strony internetowe</li>
  <li>Server Side / Dynamiczny - wyświetlanie strony internetowej w zależności od zapisanych danych</li>
  <li>Client Side / Statyczny - cache przeglądarki internetowej</li>
  <li>Client Side / Dynamiczny - początki języka <strong>JavaScript</strong></li>
</ul>

<p>Na bazie różnych kategorii powstało wiele podejść oraz technologii, które pozwalają realizować końcowe produkty.</p>

<p>Świat <strong>F#</strong> <a href="https://fsharp.org/guides/web/" title="F# web">oferuje wiele</a> z programowania webowego. Moją uwagę przykuło <a href="https://fsbolero.io/" title="F# Bolero">Bolero</a>.</p>

<p><img src="/assets/fsharp_bolero/bolero.jpeg" alt="Picture1" /></p>

<p>Jakbym miał zobrazować, na czym to polega to, zrobiłbym to tak:</p>

<p><img src="/assets/fsharp_bolero/bolero_arch.svg" alt="Picture2" /></p>

<p><strong>Bolero</strong> umożliwia pisanie aplikacji webowych w języku <strong>F#</strong>. Kod konstruuje się w podejściu <a href="https://elmish.github.io/elmish/" title="Elmish">Elmish</a> - implementacji architektury <a href="https://guide.elm-lang.org/architecture/" title="Elm architecture">Model-View-Update</a>, zapoczątkowanej wraz z pojawieniem się języka <a href="https://elm-lang.org/" title="Elm lang">Elm</a>. Wynikiem kompilacji jest program, który za pomocą <a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor" title="Blazor">Blazor</a> oraz <a href="https://dotnet.microsoft.com/" title="dotnet">.NET</a> uruchamiany jest w środowisku <a href="https://webassembly.org/" title="WebAssembly">WebAssembly</a> - alternatywy dla środowiska <strong>JavaScript</strong>.</p>

<h2 id="simple-program">Simple Program</h2>

<p>Najszybszy sposób zapoznania się z możliwościami <strong>Bolero</strong>, to wygenerowanie przykładowego projektu, postępując wg wskazówek <a href="https://fsbolero.io/" title="F# Bolero">ze strony</a>. Utworzony program zawiera implementację głównych feature’ów. Kod, generowany jest na podstawie <a href="https://github.com/fsbolero/Template" title="F# Bolero template">Template’u</a>, przygotowanego przez <a href="https://github.com/Tarmil" title="Tarmil">Loïc Denuzière</a> - <strong>autora</strong> Bolero.</p>

<p><strong>Template</strong> zawiera parametry, którymi można konfigurować końcowy efekt. Najprostszy program można wygenerować, postępując wg kroków:</p>

<ul>
  <li>zainstalować <strong>Template</strong></li>
</ul>

<figure class="highlight"><pre><code class="language-text" data-lang="text">dotnet new -i Bolero.Templates</code></pre></figure>

<ul>
  <li>utworzyć projekt</li>
</ul>

<figure class="highlight"><pre><code class="language-text" data-lang="text">dotnet new bolero-app -o Simple --minimal=true --server=false</code></pre></figure>

<ul>
  <li>uruchomić program</li>
</ul>

<figure class="highlight"><pre><code class="language-text" data-lang="text">cd Simple
dotnet run -p src/Simple.Client</code></pre></figure>

<ul>
  <li>otworzyć stronę wpisując w przeglądarkę adres wypisany na konsoli np.</li>
</ul>

<figure class="highlight"><pre><code class="language-text" data-lang="text">http://localhost:5000</code></pre></figure>

<p>Najważniejsze elementy wygenerowanego programu to:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Main.fs</code> - plik z głównym kodem</li>
  <li><code class="language-plaintext highlighter-rouge">Startup.fs</code> - plik z konfiguracją</li>
  <li><code class="language-plaintext highlighter-rouge">wwwroot</code> - katalog zawierający statyczne elementy</li>
</ul>

<p>Główy kod programu wygląda tak:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">module</span> <span class="nn">Simple</span><span class="p">.</span><span class="nn">Client</span><span class="p">.</span><span class="nc">Main</span>

<span class="k">open</span> <span class="nc">Elmish</span>
<span class="k">open</span> <span class="nc">Bolero</span>
<span class="k">open</span> <span class="nn">Bolero</span><span class="p">.</span><span class="nc">Html</span>

<span class="k">type</span> <span class="nc">Model</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">x</span><span class="p">:</span> <span class="kt">string</span>
    <span class="p">}</span>

<span class="k">let</span> <span class="n">initModel</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">x</span> <span class="p">=</span> <span class="s2">""</span>
    <span class="p">}</span>

<span class="k">type</span> <span class="nc">Message</span> <span class="p">=</span>
    <span class="p">|</span> <span class="nc">Ping</span>

<span class="k">let</span> <span class="n">update</span> <span class="n">message</span> <span class="n">model</span> <span class="p">=</span>
    <span class="k">match</span> <span class="n">message</span> <span class="k">with</span>
    <span class="p">|</span> <span class="nc">Ping</span> <span class="p">-&gt;</span> <span class="n">model</span>

<span class="k">let</span> <span class="n">view</span> <span class="n">model</span> <span class="n">dispatch</span> <span class="p">=</span>
    <span class="n">text</span> <span class="s2">"Hello, world!"</span>

<span class="k">type</span> <span class="nc">MyApp</span><span class="bp">()</span> <span class="p">=</span>
    <span class="k">inherit</span> <span class="nc">ProgramComponent</span><span class="p">&lt;</span><span class="nc">Model</span><span class="p">,</span> <span class="nc">Message</span><span class="o">&gt;()</span>

    <span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">Program</span> <span class="p">=</span>
        <span class="nn">Program</span><span class="p">.</span><span class="n">mkSimple</span> <span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">initModel</span><span class="p">)</span> <span class="n">update</span> <span class="n">view</span></code></pre></figure>

<p>Przejdźmy przez poszczególne elementy:</p>

<h4 id="klasa-myapp">Klasa MyApp</h4>

<p>Komponent ładowany przy starcie. Plik <code class="language-plaintext highlighter-rouge">Startup.fs</code> zawiera konfigurację uruchomienia:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="n">builder</span><span class="p">.</span><span class="nn">RootComponents</span><span class="p">.</span><span class="nc">Add</span><span class="p">&lt;</span><span class="nn">Main</span><span class="p">.</span><span class="nc">MyApp</span><span class="o">&gt;(</span><span class="s2">"#main"</span><span class="p">)</span></code></pre></figure>

<p>Klasa <code class="language-plaintext highlighter-rouge">MyApp</code> dziedziczy po klasie <code class="language-plaintext highlighter-rouge">ProgramComponent</code>, która w parametrach generycznych przyjmuje <code class="language-plaintext highlighter-rouge">Model</code> oraz <code class="language-plaintext highlighter-rouge">Message</code>. Uruchomienie następuje w momencie wywołania funkcji <code class="language-plaintext highlighter-rouge">mkSimple</code>, która w parametrach przyjmuje opisane poniżej wartości.</p>

<h4 id="funkcja-view">Funkcja view</h4>

<p>Renderuje widok, czyli to wszystko, co użytkownik widzi na ekranie. Posiada dostęp do aktualnego stanu programu w postaci parametru <code class="language-plaintext highlighter-rouge">model</code>. Drugi parametr <code class="language-plaintext highlighter-rouge">dispatch</code> pozwala wysyłać <strong>Message’e</strong> do programu, które umożliwiają kodowanie zachowania.</p>

<h4 id="funkcja-update">Funkcja update</h4>

<p>Uruchamiana w momencie obsługi <strong>Message’a</strong>. Parametr <code class="language-plaintext highlighter-rouge">message</code> określa jego rodzaj. Stan aktualizuje się, zmieniając wartości w parametrze <code class="language-plaintext highlighter-rouge">model</code>.</p>

<h4 id="discriminated-union-message">Discriminated Union Message</h4>

<p>Definiuje rodzaje <strong>Message’y</strong>, jakie można zgłaszać np. sygnalizacja naciśnięcia odpowiedniego przycisku na stronie.</p>

<h4 id="value-initmodel">Value initModel</h4>

<p>Początkowy stan programu, inicjalizowany przez <code class="language-plaintext highlighter-rouge">mkSimple</code>.</p>

<h4 id="record-model">Record Model</h4>

<p>Model reprezentujący aktualny stan programu.</p>

<p>Powyższy przykład zawiera najprostszą logikę działania - wypisanie tekstu na ekranie. Nie wykorzystuje on wszystkich wymienionych właściwości. Działanie kończy się na funkcji <code class="language-plaintext highlighter-rouge">view</code>. Zmieńmy to implementując prosty licznik.</p>

<h2 id="simple-counter">Simple Counter</h2>

<p>Definiujemy <code class="language-plaintext highlighter-rouge">Model</code> tak, aby przechowywał aktualną wartość licznika</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Model</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">value</span><span class="p">:</span> <span class="kt">int</span>
    <span class="p">}</span></code></pre></figure>

<p>Ustawiamy początkową wartość</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">initModel</span> <span class="p">=</span>
    <span class="p">{</span>
        <span class="n">value</span> <span class="p">=</span> <span class="mi">0</span>
    <span class="p">}</span></code></pre></figure>

<p>Definiujemy <strong>Message’e</strong></p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Increment</code> -  przesuwa licznik do przodu</li>
  <li><code class="language-plaintext highlighter-rouge">Decrement</code> - przesuwa licznik do tyłu</li>
  <li><code class="language-plaintext highlighter-rouge">Reset</code> - ustawia początkową wartość</li>
</ul>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">type</span> <span class="nc">Message</span> <span class="p">=</span>
    <span class="p">|</span> <span class="nc">Increment</span>
    <span class="p">|</span> <span class="nc">Decrement</span>
    <span class="p">|</span> <span class="nc">Reset</span></code></pre></figure>

<p>Aktualizujemy funkcję <code class="language-plaintext highlighter-rouge">update</code>, ustawiając odpowiednią wartość licznika w zależności od <strong>Message’a</strong></p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">update</span> <span class="n">message</span> <span class="n">model</span> <span class="p">=</span>
    <span class="k">match</span> <span class="n">message</span> <span class="k">with</span>
    <span class="p">|</span> <span class="nc">Increment</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">value</span> <span class="p">=</span> <span class="n">model</span><span class="p">.</span><span class="n">value</span> <span class="o">+</span> <span class="mi">1</span> <span class="p">}</span>
    <span class="p">|</span> <span class="nc">Decrement</span> <span class="p">-&gt;</span> <span class="p">{</span><span class="n">model</span> <span class="k">with</span> <span class="n">value</span> <span class="p">=</span> <span class="n">model</span><span class="p">.</span><span class="n">value</span> <span class="p">-</span> <span class="mi">1</span> <span class="p">}</span>
    <span class="p">|</span> <span class="nc">Reset</span> <span class="p">-&gt;</span> <span class="p">{</span> <span class="n">model</span> <span class="k">with</span> <span class="n">value</span> <span class="p">=</span> <span class="mi">0</span> <span class="p">}</span></code></pre></figure>

<p>Aktualizujemy funkcję <code class="language-plaintext highlighter-rouge">view</code> tak, aby wyświetliła interfejs użytkownika zawierający:</p>

<ul>
  <li>pokazanie aktualnej wartości licznika</li>
  <li>przyciski przesunięcia licznika w dwie strony</li>
  <li>zresetowanie licznika</li>
</ul>

<p>Dodatkowo funkcja zgłasza odpowiedni <strong>Message</strong> w zależności od tego, który przycisk zostanie kliknięty.</p>

<p>Poniższy kod pokazuje jedną z właściwości <strong>Bolero</strong> - konstruowanie UI w kodzie <strong>F#</strong>:</p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">let</span> <span class="n">view</span> <span class="n">model</span> <span class="n">dispatch</span> <span class="p">=</span>
    <span class="n">concat</span> <span class="p">[</span>
        <span class="n">div</span> <span class="bp">[]</span> <span class="p">[</span>
            <span class="n">button</span> <span class="p">[</span><span class="n">on</span><span class="p">.</span><span class="n">click</span> <span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">Decrement</span><span class="o">)]</span> <span class="p">[</span><span class="n">text</span> <span class="s2">"-"</span><span class="p">]</span>
            <span class="n">text</span> <span class="p">(</span><span class="kt">string</span> <span class="n">model</span><span class="p">.</span><span class="n">value</span><span class="p">)</span>
            <span class="n">button</span> <span class="p">[</span><span class="n">on</span><span class="p">.</span><span class="n">click</span> <span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">Increment</span><span class="o">)]</span> <span class="p">[</span><span class="n">text</span> <span class="s2">"+"</span><span class="p">]</span>
        <span class="p">]</span>

        <span class="n">div</span> <span class="bp">[]</span> <span class="p">[</span>
            <span class="n">button</span> <span class="p">[</span><span class="n">on</span><span class="p">.</span><span class="n">click</span> <span class="p">(</span><span class="k">fun</span> <span class="p">_</span> <span class="p">-&gt;</span> <span class="n">dispatch</span> <span class="nc">Reset</span><span class="o">)]</span> <span class="p">[</span><span class="n">text</span> <span class="s2">"Reset"</span><span class="p">]</span>
        <span class="p">]</span>
    <span class="p">]</span></code></pre></figure>

<p>Uruchamiamy program</p>

<figure class="highlight"><pre><code class="language-text" data-lang="text">dotnet run -p src/Simple.Client</code></pre></figure>

<p>Sprawdzamy efekt w przeglądarce internetowej</p>

<p><img src="/assets/fsharp_bolero/bolero_counter.png" alt="Picture3" /></p>

<p>Zgodnie z implementacją przycisk <code class="language-plaintext highlighter-rouge">+</code> zwiększa wartość licznika, a przycisk <code class="language-plaintext highlighter-rouge">-</code> zmniejsza. Przycisk <code class="language-plaintext highlighter-rouge">Reset</code> ustawia początkową wartość.</p>

<p>Istnieje jeszcze jeden, bardziej znany, sposób konstruowania UI - za pomocą standardowych znaczników <strong>HTML</strong>. Strona napisana w <strong>HTML</strong> reprezentowana jest w kodzie <strong>Bolero</strong> w postaci tzw. <strong>Templates</strong>.</p>

<h2 id="początek-serii">Początek Serii</h2>

<p>Spędziłem wiele miesięcy w poszukiwaniu sposobu programowania webowego, który najbardziej by mi odpowiadał. Taki sposób znalazłem w świecie <strong>F#</strong>. Obecnie zapoznaję się z różnymi możliwościami <strong>Bolero</strong>. Wyniki będę umieszczał na <a href="https://github.com/mikedevbo/fsharp-bolero-sandbox" title="fsharp-bolero-sandbox">GitHub’e</a>, a poszczególne elementy opisywał w serii, której pierwszy artykuł właśnie czytasz. Zachęcam Cię do własnych eksperymentów. Może taki sposób pisania kodu będzie odpowiadał również Tobie.</p>

<p>W następnym artykule zobaczymy, w jaki sposób używać wyżej wymienionych <strong>Templates</strong>.</p>

<p>=</p>

<h3> </h3>]]></content><author><name>mikedevbo</name></author><summary type="html"><![CDATA[Wstęp do F# Bolero. W tym artykule przeczytasz, w jaki sposób można napisać najprostszy program w Bolero.]]></summary></entry><entry><title type="html">2021 - Co było…Co jest…Co będzie…</title><link href="/2021/01/06/what-was-what-is-what-will-be.html" rel="alternate" type="text/html" title="2021 - Co było…Co jest…Co będzie…" /><published>2021-01-06T00:00:00+00:00</published><updated>2021-01-06T00:00:00+00:00</updated><id>/2021/01/06/what-was-what-is-what-will-be</id><content type="html" xml:base="/2021/01/06/what-was-what-is-what-will-be.html"><![CDATA[<p>Nowy Rok. Nowa energia do działania. Nowe… Czas szybko płynie. Nie tak dawno pisałem <a href="/2020/01/12/what-was-what-is-what-will-be.html">poprzednie podsumowanie</a>, a teraz przyszła pora na kolejne. Tym razem obejmie ono krótszy okres - rok 2020. Podzielę się również planami na rok 2021. Udanego czytania!</p>

<h2 id="co-było">Co było…</h2>

<p><em>“Jeden obraz wart więcej niż tysiąc słów”</em> (chińskie przysłowie):</p>

<p><a href="/assets/2021_was_is_will_be/what_was.svg"><img src="/assets/2021_was_is_will_be/what_was.svg" alt="Picture1" /></a></p>

<p>Na początku 2020 roku, zdefiniowałem dla siebie długofalowy cel:</p>

<ul>
  <li>nauczyć się realizować systemy posiadające <strong>Frontend</strong>, <strong>Backend</strong> oraz <strong>integrację z systemami zewnętrznymi</strong></li>
</ul>

<p>Wybrałem podstawę do budowania wiedzy:</p>

<ul>
  <li>kurs <a href="https://particular.net/adsd" title="Particular ADSD">Advanced Distributed Systems Design (ADSD)</a></li>
</ul>

<p>Wypisałem wzorce oraz narzędzia, które pomogą mi zrealizować główny cel w taki sposób, aby końcowe rezultaty cechowały się dobrą, a może nawet i bardzo dobrą jakością:</p>

<ul>
  <li>elementy widoczne na powyższym zdjęciu</li>
</ul>

<p>Postanowiłem opisywać na blogu rezultaty oraz dzielić się przemyśleniami na temat poznanego zagadnienia.</p>

<h2 id="co-jest">Co jest…</h2>

<p><em>“Jeden obraz wart więcej niż tysiąc słów”</em> (chińskie przysłowie):</p>

<p><a href="/assets/2021_was_is_will_be/what_is.svg"><img src="/assets/2021_was_is_will_be/what_is.svg" alt="Picture2" /></a></p>

<p>W kolejnych miesiącach 2020 roku zapoznawałem się z poszczególnymi elementami oraz utrwalałem to, co już znałem. Obecny stan wiedzy podzieliłem na trzy kategorie:</p>

<ul>
  <li>“siadam i robię” - kolor zielony na zdjęciu powyżej
    <ul>
      <li>elementy, które znam na tyle dobrze, że jestem w stanie używać ich na co dzień
        <ul>
          <li>metafora sportowa - wychodzę na mecz i gram
<br />
<br /></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>“robię, ale…” - kolor niebieski na zdjęciu powyżej
    <ul>
      <li>elementy, które znam w praktyce, ale potrzebuję większego wsparcia w postaci wyszukiwania i analizowania pomocnych treści
        <ul>
          <li>metafora sportowa - mogę wyjść na mecz, ale będą widoczne braki, trzeba więcej trenować
<br />
<br /></li>
        </ul>
      </li>
    </ul>
  </li>
  <li>“tylko teoria” - kolor czarny na zdjęciu powyżej
    <ul>
      <li>elementy, o których mogę podyskutować, znam idee, ale nie używałem ich w praktyce
        <ul>
          <li>metafora sportowa - nie wyjdę na mecz, najpierw muszę chociaż trochę potrenować</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<p>A jak to wygląda w szczegółach? Zobaczmy.</p>

<h3 id="design">Design</h3>

<p>W związku z sytuacją <em>COVID-19</em>, zespół rozwijający platformę <a href="https://particular.net/" title="Particular.net">Particular</a> postanowił czasowo <a href="https://discuss.particular.net/t/free-for-a-limited-time-advanced-distributed-systems-design-videos/1708" title="free for a limited time advanced distributed systems design videos">udostępnić</a> kurs online <a href="https://learn.particular.net/courses/adsd-online" title="ADSD course online">ADSD</a> za tzw. <strong>free</strong>. Udało mi się ukończyć kurs, czego rezultatem jest pogłębienie wiedzy z proponowanych w nim idei.</p>

<p>Efektem utrwalonej wiedzy jest to, że przesunąłem do kategorii “siadam i robię” zagadnienie dotyczące <strong>Domain Model</strong>. Koncepcja <strong>Sagi</strong> bardzo mi odpowiada. Jest prosta i elegancka. Framework <a href="https://particular.net/nservicebus" title="NServiceBus">NServiceBus</a> zawiera jej implementację, także od razu można korzystać z niej w praktyce.</p>

<p>Wiedząc jak realizować <strong>Domain Model</strong>, automatycznie mamy rozwiązany sposób realizacji części <strong>Command</strong> w podejściu <strong>CQRS</strong>. Część <strong>Query</strong> to jak najprostsze pobranie informacji potrzebnych do realizacji wymagania biznesowego. Do ustalenia pozostało mi uporządkowanie wiedzy na temat efektywnego modelu przechowywanych danych dla <strong>Query</strong>. Jak się z tym uporam, to będę mógł przenieść zagadnienie <strong>CQRS</strong> do pierwszej kategorii.</p>

<p>Z tematyką <strong>Frontend</strong> poszło trochę wolniej niż zakładałem. Przez cały poprzedni rok poszukiwałem drogi, którą chciałbym podążyć. Znalazłem pewien kierunek, który będę chciał przetestować. Więcej o tym przeczytasz w dalszej części artykułu.</p>

<p><strong>Frontend</strong> jest ostatnim elementem, który blokuje mnie przed przeniesieniem do pierwszej kategorii zagadnień <strong>SOA</strong> oraz <strong>ADSD</strong>.</p>

<h3 id="develop">Develop</h3>

<p>Minął kolejny rok od momentu, w którym rozpocząłem przygodę z frameworkiem <a href="https://particular.net/nservicebus" title="NServiceBus">NServiceBus</a> oraz platformą <a href="https://particular.net/" title="Particular.net">Particular</a>. W sumie to już 7 lat. W roku 2020 podobnie, jak w poprzednich latach platforma pokazała swoją siłę w najtrudniejszych elementach związanych z wytwarzaniem oprogramowania:</p>

<ul>
  <li>utrzymaniem oraz dalszym rozwojem istniejących funkcjonalności</li>
</ul>

<p>Kodowanie <strong>Message Handlerów</strong> to wciąż czysta przyjemność.</p>

<p>Zdolność udzielania użytkownikom odpowiedzi na pytania, dlaczego stan systemu wygląda tak, a nie inaczej sprawia wiele satysfakcji. Taką możliwość daje przeglądanie audytu przetworzonych wiadomości za pomocą narzędzia <a href="https://particular.net/serviceinsight" title="ServiceInsight">ServiceInsight</a>.</p>

<p>Monitoring za pomocą narzędzia <a href="https://particular.net/servicepulse" title="ServicePulse">ServicePulse</a> to bajka. Na jednym ekranie widać, czy i w jakim tempie mechanizmy przetwarzają wiadomości. Istnieje możliwość podglądu danych w czasie rzeczywistym. Krótko mówiąc, widać, czy system działa, czy jest responsywny, czy może jednak się “zapycha”.</p>

<p>Wisienką na torcie jest koncepcja <a href="https://docs.particular.net/tutorials/message-replay/" title="Message Replay Tutorial">Recoverability</a>. Mimo tylu lat używania platformy, za każdym razem, kiedy widzę w <a href="https://particular.net/servicepulse" title="ServicePulse">ServicePulse</a> ponowne przetwarzanie przywróconych wiadomości, generuje mi się taki “mały” uśmiech na twarzy z myślą, ile właśnie zaoszczędziłem czasu na utrzymanie systemu w spójnym stanie.</p>

<p>Zeszły rok to również kontynuacja nauki języka <strong>F#</strong> oraz powiązanych z nim technologii. Koncepcyjnie jest coraz lepiej, jednak do wyrównania poziomu kodowania w języku <strong>C#</strong> potrzebuję większej ilości treningów.</p>

<p>W przypadku <strong>Git</strong> i <strong>GitHub</strong> nic się nie zmieniło. Nie miałem konieczności używania czegoś więcej niż to, co znam obecnie.</p>

<h3 id="test">Test</h3>

<p>Z tematyką testów również nic się nie zmieniło. Jak na razie w zupełności wystarcza dzielenie kodu na małe, niezależne części, które można przetestować za pomocą automatycznych testów jednostkowych oraz manualnych testów integracyjnych. <strong>Property-Based Testing</strong>, a także automatyzacja testów integracyjnych/wydajnościowych czekają na swoją kolej.</p>

<h3 id="deploy">Deploy</h3>

<p>Poznawanie narzędzia <a href="https://fake.build/" title="Fake build">Fake</a> dało mi możliwość potrenowania pisania kodu w języku <strong>F#</strong>. Napisałem mechanizm wdrażania nowych wersji systemu komentarzy, którego używam na tym blogu. <strong>Microsoft Azure</strong>? Trochę poklikałem, ale jeszcze zostawiam temat na stanie “tylko teoria”.</p>

<h3 id="ddtd">DDTD</h3>

<p>Tak wygląda mój obecny stan wiedzy z elementów, które pomogą mi osiągnąć główny cel:</p>

<ul>
  <li>realizować systemy posiadające <strong>Frontend</strong>, <strong>Backend</strong> oraz <strong>integrację z systemami zewnętrznymi</strong></li>
</ul>

<p>Wrócę jeszcze do platformy <a href="https://particular.net/" title="Particular.net">Particular</a>. W zeszłym roku nastąpiła ważna zmiana w sposobie jej licencjonowania. Obecnie można używać całej platformy <strong>bez opłat</strong> na etapach wytwarzania oraz testowania funkcjonalności. Jeśli końcowy produkt nie przekroczył pewnej skali, to cała platforma dostępna jest również <strong>bez opłat</strong> do użytku na środowisku produkcyjnym. 
Dzięki temu łatwiej jest zacząć z realizacją produktu. Więcej informacji znajdziesz <a href="https://particular.net/pricing" title="Pricing">tu</a>.</p>

<h3 id="blog">Blog</h3>

<p>Od strony wizualnej nic się nie zmieniło. Jeśli chodzi o zawartość, to udało mi się napisać 10 artykułów. Taką samą liczbę miałem w roku 2019, także utrzymałem ilość wpisów.</p>

<p>Publikując treści na blogu wyszedłem do świata, dzieląc się wiedzą, przemyśleniami oraz doświadczeniami. Nie spodziewałem się tego, ale dzięki temu, zostałem wyróżniony przez zespół rozwijający platformę <strong>Particular</strong> i zostałem tzw. <a href="https://discuss.particular.net/t/introducing-our-newest-champ-michal-bogdanski/1840" title="introducing champ">NServiceBus Champ</a>. Bardzo miłe doświadczenie.</p>

<h2 id="co-będzie">Co będzie…</h2>

<p><em>“Jeden obraz wart więcej niż tysiąc słów”</em> (chińskie przysłowie):</p>

<p><a href="/assets/2021_was_is_will_be/what_will_be.svg"><img src="/assets/2021_was_is_will_be/what_will_be.svg" alt="Picture3" /></a></p>

<p>Nie zmieniam celu długofalowego. W każdym kolejnym roku będę wzmacniał wiedzę z tego, co już znam, podnosił umiejętności w tematach, które poznałem, a także zapoznawał się z tym, czego nie udało mi się rozpocząć. Jeśli wytrwam, to za jakiś czas wszystkie elementy widoczne na powyższym zdjęciu zapalą się na zielono - osiągną stan “siadam i robię”.</p>

<p>Odnośnie tematu <strong>Frontend</strong>, to w tym roku przetestuję podejście, które mnie zaciekawiło - <a href="https://jamstack.org/" title="jamstack">JAMstack</a>. Wprowadzę jedną modyfikację - zamienię <strong>J</strong> na <strong>C</strong> i/lub <strong>F</strong>. Od strony <strong>Develop</strong> będą to <a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor" title="blazor">Blazor</a> oraz <a href="https://fsbolero.io/" title="bolero">Bolero</a>.</p>

<p>Plany związane z blogiem nie ulegają zmianie. Będę dzielił się tym, co uda mi się osiągnąć oraz przemyśleniami na temat poznanego zagadnienia.</p>

<p>To tyle, jeśli chodzi o podsumowanie minionego roku oraz plany na najbliższą, oraz dalszą przyszłość. Dzięki za przeczytanie artykułu. Powodzenia z Twoimi planami oraz ich realizacją!</p>

<p>=</p>

<h3> </h3>]]></content><author><name>mikedevbo</name></author><summary type="html"><![CDATA[...a teraz przyszła pora na kolejne. Tym razem obejmie ono krótszy okres - rok 2020. Podzielę się również planami na rok 2021. Udanego czytania!]]></summary></entry><entry><title type="html">F# 5 - NServiceBus Saga - idealne dopasowanie</title><link href="/2020/11/08/fsharp-5-nservicebus-saga-perfect-match.html" rel="alternate" type="text/html" title="F# 5 - NServiceBus Saga - idealne dopasowanie" /><published>2020-11-08T00:00:00+00:00</published><updated>2020-11-08T00:00:00+00:00</updated><id>/2020/11/08/fsharp-5-nservicebus-saga-perfect-match</id><content type="html" xml:base="/2020/11/08/fsharp-5-nservicebus-saga-perfect-match.html"><![CDATA[<p>Wydanie <strong>5’tej</strong> wersji frameworka .NET <a href="https://devblogs.microsoft.com/dotnet/net-5-0-launches-at-net-conf-november-10-12/" title="net 5 launches at net conf november 10-12">zbliża się wielkimi krokami</a>.  W tym samym czasie <a href="https://devblogs.microsoft.com/dotnet/f-5-update-for-august/" title="f# 5 update for august/">pojawi się nowa wersja</a> <strong>F#</strong>, która również będzie oznaczona numerem <strong>5</strong>. Jedna z nowych właściwości języka to:</p>

<p><a href="https://devblogs.microsoft.com/dotnet/f-5-update-for-august/#interfaces-can-be-implemented-at-different-generic-instantiations" title="interfaces can be implemented at different generic instantiations">Allow implementing the same interface at different generic instantiations</a></p>

<p>Jest to kluczowa zmiana w kontekście implementacji <strong>Sagi</strong> frameworka <strong>NServiceBus</strong>.</p>

<p>Kod <strong>Sagi</strong> w 4’tej wersji <strong>F#</strong> trzeba rozbijać na osobne klasy, które implementują interfejs <code class="language-plaintext highlighter-rouge">IAmStartedByMessages</code> lub <code class="language-plaintext highlighter-rouge">IHandleMessages</code> z konkretnym typem generycznym. Szczegóły takiego sposobu implementacji opisałem w <a href="/2019/12/15/fsharp-nservicebus-practical-guide-configure-and-use-saga.html">szóstej części</a> praktycznego przewodnika pokazującego, w jaki sposób można połączyć framework <strong>NServiceBus</strong> oraz język <strong>F#</strong>.</p>

<p>Kod <strong>Sagi</strong> w 5’tej wersji <strong>F#</strong> można napisać w taki sam sposób jak kod napisany w <strong>C#</strong>, przedstawiony <a href="https://docs.particular.net/tutorials/nservicebus-sagas/1-getting-started/" title="NServiceBus Saga getting started">w dokumentacji</a> frameworka. Dzięki temu implementacja upraszcza się i jest spójna w obydwu językach.</p>

<p>Porównajmy kod przed i po wprowadzeniu zmian.</p>

<h2 id="nservicebus-saga---f-4">NServiceBus Saga - F# 4</h2>

<p>Kod dostępny jest <a href="https://github.com/mikedevbo/fsharp-nservicebus/tree/fsharp-4" title="mikedevbo F# 4 NServiceBus">na moim koncie GitHub</a></p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">module</span> <span class="nc">ShippingPolicy</span>

<span class="k">open</span> <span class="nc">NServiceBus</span>
<span class="k">open</span> <span class="nc">Events</span>
<span class="k">open</span> <span class="nn">NServiceBus</span><span class="p">.</span><span class="nc">Logging</span>
<span class="k">open</span> <span class="nn">System</span><span class="p">.</span><span class="nn">Threading</span><span class="p">.</span><span class="nc">Tasks</span>
<span class="k">open</span> <span class="nc">Commands</span>

<span class="k">type</span> <span class="nc">ShippingPolicyData</span><span class="bp">()</span> <span class="p">=</span>
    <span class="k">inherit</span> <span class="nc">ContainSagaData</span><span class="bp">()</span>
    <span class="k">member</span> <span class="k">val</span> <span class="nc">OrderId</span> <span class="p">=</span> <span class="s2">""</span> <span class="k">with</span> <span class="n">get</span><span class="p">,</span><span class="n">set</span>
    <span class="k">member</span> <span class="k">val</span> <span class="nc">IsOrderPlaced</span> <span class="p">=</span> <span class="bp">false</span> <span class="k">with</span> <span class="n">get</span><span class="p">,</span><span class="n">set</span>
    <span class="k">member</span> <span class="k">val</span> <span class="nc">IsOrderBilled</span> <span class="p">=</span> <span class="bp">false</span> <span class="k">with</span> <span class="n">get</span><span class="p">,</span><span class="n">set</span>

<span class="k">let</span> <span class="n">canShipOrder</span> <span class="p">(</span><span class="n">policyData</span><span class="p">:</span> <span class="nc">ShippingPolicyData</span><span class="p">)</span> <span class="p">=</span>
    <span class="n">policyData</span><span class="p">.</span><span class="nc">IsOrderPlaced</span> <span class="p">&amp;&amp;</span> <span class="n">policyData</span><span class="p">.</span><span class="nc">IsOrderBilled</span>

<span class="k">let</span> <span class="n">shipOrder</span> <span class="n">orderId</span> <span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">IMessageHandlerContext</span><span class="p">)</span> <span class="p">=</span>
    <span class="k">let</span> <span class="n">shipOrder</span> <span class="p">=</span> <span class="k">new</span> <span class="nc">ShipOrder</span><span class="p">(</span><span class="n">orderId</span><span class="p">)</span>
    <span class="n">context</span><span class="p">.</span><span class="nc">SendLocal</span><span class="p">(</span><span class="n">shipOrder</span><span class="p">)</span>

<span class="k">type</span> <span class="nc">ShippingPolicyOrderPlaced</span><span class="bp">()</span> <span class="p">=</span>
    <span class="k">inherit</span> <span class="nc">Saga</span><span class="p">&lt;</span><span class="nc">ShippingPolicyData</span><span class="o">&gt;()</span>
        <span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">ConfigureHowToFindSaga</span><span class="p">(</span><span class="n">mapper</span><span class="p">:</span> <span class="nc">SagaPropertyMapper</span><span class="p">&lt;</span><span class="nc">ShippingPolicyData</span><span class="o">&gt;)</span> <span class="p">=</span>
            <span class="n">mapper</span><span class="p">.</span><span class="nc">ConfigureMapping</span><span class="p">&lt;</span><span class="nc">OrderPlaced</span><span class="o">&gt;(</span><span class="k">fun</span> <span class="n">message</span> <span class="p">-&gt;</span> <span class="n">message</span><span class="p">.</span><span class="nc">OrderId</span> <span class="p">:&gt;</span> <span class="n">obj</span><span class="o">).</span><span class="nc">ToSaga</span><span class="p">(</span><span class="k">fun</span> <span class="n">sagaData</span> <span class="p">-&gt;</span> <span class="n">sagaData</span><span class="p">.</span><span class="nc">OrderId</span> <span class="p">:&gt;</span> <span class="n">obj</span><span class="p">)</span>
    
    <span class="k">static</span> <span class="k">member</span> <span class="n">log</span> <span class="p">=</span> <span class="nn">LogManager</span><span class="p">.</span><span class="nc">GetLogger</span><span class="p">&lt;</span><span class="nc">ShippingPolicyOrderPlaced</span><span class="o">&gt;()</span>
    
    <span class="k">interface</span> <span class="nc">IAmStartedByMessages</span><span class="p">&lt;</span><span class="nc">OrderPlaced</span><span class="p">&gt;</span> <span class="k">with</span>
        <span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">Handle</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">context</span><span class="p">)</span> <span class="p">=</span> 
            <span class="nn">ShippingPolicyOrderPlaced</span><span class="p">.</span><span class="n">log</span><span class="p">.</span><span class="nc">Info</span><span class="p">(</span><span class="s2">"OrderPlaced message received."</span><span class="p">)</span>
            
            <span class="n">this</span><span class="p">.</span><span class="nn">Data</span><span class="p">.</span><span class="nc">IsOrderPlaced</span> <span class="p">&lt;-</span> <span class="bp">true</span>
            
            <span class="k">let</span> <span class="n">canShipOrder</span> <span class="p">=</span> <span class="n">canShipOrder</span> <span class="n">this</span><span class="p">.</span><span class="nc">Data</span>
            <span class="k">match</span> <span class="n">canShipOrder</span> <span class="k">with</span>
            <span class="p">|</span> <span class="bp">true</span> <span class="p">-&gt;</span>
                <span class="n">this</span><span class="p">.</span><span class="nc">MarkAsComplete</span><span class="bp">()</span>
                <span class="n">shipOrder</span> <span class="n">this</span><span class="p">.</span><span class="nn">Data</span><span class="p">.</span><span class="nc">OrderId</span> <span class="n">context</span>
            <span class="p">|</span> <span class="bp">false</span> <span class="p">-&gt;</span>
                <span class="nn">Task</span><span class="p">.</span><span class="nc">CompletedTask</span>

<span class="k">type</span> <span class="nc">ShippingPolicyOrderBilled</span><span class="bp">()</span> <span class="p">=</span>
    <span class="k">inherit</span> <span class="nc">Saga</span><span class="p">&lt;</span><span class="nc">ShippingPolicyData</span><span class="o">&gt;()</span>
        <span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">ConfigureHowToFindSaga</span><span class="p">(</span><span class="n">mapper</span><span class="p">:</span> <span class="nc">SagaPropertyMapper</span><span class="p">&lt;</span><span class="nc">ShippingPolicyData</span><span class="o">&gt;)</span> <span class="p">=</span>
            <span class="n">mapper</span><span class="p">.</span><span class="nc">ConfigureMapping</span><span class="p">&lt;</span><span class="nc">OrderBilled</span><span class="o">&gt;(</span><span class="k">fun</span> <span class="n">message</span> <span class="p">-&gt;</span> <span class="n">message</span><span class="p">.</span><span class="nc">OrderId</span> <span class="p">:&gt;</span> <span class="n">obj</span><span class="o">).</span><span class="nc">ToSaga</span><span class="p">(</span><span class="k">fun</span> <span class="n">sagaData</span> <span class="p">-&gt;</span> <span class="n">sagaData</span><span class="p">.</span><span class="nc">OrderId</span> <span class="p">:&gt;</span> <span class="n">obj</span><span class="p">)</span>
            
    <span class="k">static</span> <span class="k">member</span> <span class="n">log</span> <span class="p">=</span> <span class="nn">LogManager</span><span class="p">.</span><span class="nc">GetLogger</span><span class="p">&lt;</span><span class="nc">ShippingPolicyOrderBilled</span><span class="o">&gt;()</span>
    
    <span class="k">interface</span> <span class="nc">IAmStartedByMessages</span><span class="p">&lt;</span><span class="nc">OrderBilled</span><span class="p">&gt;</span> <span class="k">with</span>
        <span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">Handle</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">context</span><span class="p">)</span> <span class="p">=</span> 
            <span class="nn">ShippingPolicyOrderBilled</span><span class="p">.</span><span class="n">log</span><span class="p">.</span><span class="nc">Info</span><span class="p">(</span><span class="s2">"OrderBilled message received."</span><span class="p">)</span>
            
            <span class="n">this</span><span class="p">.</span><span class="nn">Data</span><span class="p">.</span><span class="nc">IsOrderBilled</span> <span class="p">&lt;-</span> <span class="bp">true</span>
            
            <span class="k">let</span> <span class="n">canShipOrder</span> <span class="p">=</span> <span class="n">canShipOrder</span> <span class="n">this</span><span class="p">.</span><span class="nc">Data</span>
            <span class="k">match</span> <span class="n">canShipOrder</span> <span class="k">with</span>
            <span class="p">|</span> <span class="bp">true</span> <span class="p">-&gt;</span>
                <span class="n">this</span><span class="p">.</span><span class="nc">MarkAsComplete</span><span class="bp">()</span>
                <span class="n">shipOrder</span> <span class="n">this</span><span class="p">.</span><span class="nn">Data</span><span class="p">.</span><span class="nc">OrderId</span> <span class="n">context</span>
            <span class="p">|</span> <span class="bp">false</span> <span class="p">-&gt;</span>
                <span class="nn">Task</span><span class="p">.</span><span class="nc">CompletedTask</span></code></pre></figure>

<p>Szczegółowy opis znajdziesz we wspomnianym już <a href="/2019/12/15/fsharp-nservicebus-practical-guide-configure-and-use-saga.html">praktycznym przewodniku</a>. Zwróć uwagę na konieczność utworzenia dwóch klas, które składają się na implementację <strong>Sagi NServiceBus</strong>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">ShippingPolicyOrderPlaced</code> - przetwarza wiadomość <code class="language-plaintext highlighter-rouge">OrderPlaced</code></li>
  <li><code class="language-plaintext highlighter-rouge">ShippingPolicyOrderBilled</code> - przetwarza wiadomość <code class="language-plaintext highlighter-rouge">OrderBilled</code></li>
</ul>

<p>Dodatkowo istnieją dwie osobne funkcje, które wykorzystywane są w obydwu klasach:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">canShipOrder</code> - zwraca informację, czy zamówienie może zostać wysłane</li>
  <li><code class="language-plaintext highlighter-rouge">shipOrder</code> - wysyła wiadomość, która inicjuje Policy wysyłki zamówienia</li>
</ul>

<p>A jak może wyglądać implementacja tej samej funkcjonalności w nowej wersji języka <strong>F#</strong>? Zobaczmy.</p>

<h2 id="nservicebus-saga---f-5">NServiceBus Saga - F# 5</h2>

<p>Kod dostępny jest <a href="https://github.com/mikedevbo/fsharp-nservicebus" title="mikedevbo F# 5 NServiceBus">na moim koncie GitHub</a></p>

<figure class="highlight"><pre><code class="language-fsharp" data-lang="fsharp"><span class="k">module</span> <span class="nc">ShippingPolicy</span>

<span class="k">open</span> <span class="nc">NServiceBus</span>
<span class="k">open</span> <span class="nc">Events</span>
<span class="k">open</span> <span class="nn">NServiceBus</span><span class="p">.</span><span class="nc">Logging</span>
<span class="k">open</span> <span class="nn">System</span><span class="p">.</span><span class="nn">Threading</span><span class="p">.</span><span class="nc">Tasks</span>
<span class="k">open</span> <span class="nc">Commands</span>

<span class="k">type</span> <span class="nc">ShippingPolicyData</span><span class="bp">()</span> <span class="p">=</span>
    <span class="k">inherit</span> <span class="nc">ContainSagaData</span><span class="bp">()</span>
    <span class="k">member</span> <span class="k">val</span> <span class="nc">OrderId</span> <span class="p">=</span> <span class="s2">""</span> <span class="k">with</span> <span class="n">get</span><span class="p">,</span><span class="n">set</span>
    <span class="k">member</span> <span class="k">val</span> <span class="nc">IsOrderPlaced</span> <span class="p">=</span> <span class="bp">false</span> <span class="k">with</span> <span class="n">get</span><span class="p">,</span><span class="n">set</span>
    <span class="k">member</span> <span class="k">val</span> <span class="nc">IsOrderBilled</span> <span class="p">=</span> <span class="bp">false</span> <span class="k">with</span> <span class="n">get</span><span class="p">,</span><span class="n">set</span>

<span class="k">type</span> <span class="nc">ShippingPolicy</span><span class="bp">()</span> <span class="p">=</span>
    <span class="k">inherit</span> <span class="nc">Saga</span><span class="p">&lt;</span><span class="nc">ShippingPolicyData</span><span class="o">&gt;()</span>
        <span class="k">override</span> <span class="n">this</span><span class="p">.</span><span class="nc">ConfigureHowToFindSaga</span><span class="p">(</span><span class="n">mapper</span><span class="p">:</span> <span class="nc">SagaPropertyMapper</span><span class="p">&lt;</span><span class="nc">ShippingPolicyData</span><span class="o">&gt;)</span> <span class="p">=</span>
            <span class="n">mapper</span><span class="p">.</span><span class="nc">ConfigureMapping</span><span class="p">&lt;</span><span class="nc">OrderPlaced</span><span class="o">&gt;(</span><span class="k">fun</span> <span class="n">message</span> <span class="p">-&gt;</span> <span class="n">message</span><span class="p">.</span><span class="nc">OrderId</span> <span class="p">:&gt;</span> <span class="n">obj</span><span class="o">).</span><span class="nc">ToSaga</span><span class="p">(</span><span class="k">fun</span> <span class="n">sagaData</span> <span class="p">-&gt;</span> <span class="n">sagaData</span><span class="p">.</span><span class="nc">OrderId</span> <span class="p">:&gt;</span> <span class="n">obj</span><span class="p">)</span>
            <span class="n">mapper</span><span class="p">.</span><span class="nc">ConfigureMapping</span><span class="p">&lt;</span><span class="nc">OrderBilled</span><span class="o">&gt;(</span><span class="k">fun</span> <span class="n">message</span> <span class="p">-&gt;</span> <span class="n">message</span><span class="p">.</span><span class="nc">OrderId</span> <span class="p">:&gt;</span> <span class="n">obj</span><span class="o">).</span><span class="nc">ToSaga</span><span class="p">(</span><span class="k">fun</span> <span class="n">sagaData</span> <span class="p">-&gt;</span> <span class="n">sagaData</span><span class="p">.</span><span class="nc">OrderId</span> <span class="p">:&gt;</span> <span class="n">obj</span><span class="p">)</span>

    <span class="k">static</span> <span class="k">member</span> <span class="n">log</span> <span class="p">=</span> <span class="nn">LogManager</span><span class="p">.</span><span class="nc">GetLogger</span><span class="p">&lt;</span><span class="nc">ShippingPolicy</span><span class="o">&gt;()</span>

    <span class="k">interface</span> <span class="nc">IAmStartedByMessages</span><span class="p">&lt;</span><span class="nc">OrderPlaced</span><span class="p">&gt;</span> <span class="k">with</span>
        <span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">Handle</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">context</span><span class="p">)</span> <span class="p">=</span>
            <span class="nn">ShippingPolicy</span><span class="p">.</span><span class="n">log</span><span class="p">.</span><span class="nc">Info</span><span class="p">(</span><span class="s2">"OrderPlaced message received."</span><span class="p">)</span>

            <span class="n">this</span><span class="p">.</span><span class="nn">Data</span><span class="p">.</span><span class="nc">IsOrderPlaced</span> <span class="p">&lt;-</span> <span class="bp">true</span>
            <span class="n">this</span><span class="p">.</span><span class="nc">ProcessOrder</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>

    <span class="k">interface</span> <span class="nc">IAmStartedByMessages</span><span class="p">&lt;</span><span class="nc">OrderBilled</span><span class="p">&gt;</span> <span class="k">with</span>
        <span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">Handle</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">context</span><span class="p">)</span> <span class="p">=</span>
            <span class="nn">ShippingPolicy</span><span class="p">.</span><span class="n">log</span><span class="p">.</span><span class="nc">Info</span><span class="p">(</span><span class="s2">"OrderBilled message received."</span><span class="p">)</span>

            <span class="n">this</span><span class="p">.</span><span class="nn">Data</span><span class="p">.</span><span class="nc">IsOrderBilled</span> <span class="p">&lt;-</span> <span class="bp">true</span>
            <span class="n">this</span><span class="p">.</span><span class="nc">ProcessOrder</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>

    <span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">ProcessOrder</span><span class="p">(</span><span class="n">context</span><span class="p">:</span><span class="nc">IMessageHandlerContext</span><span class="p">)</span> <span class="p">=</span>
        <span class="k">match</span> <span class="n">this</span><span class="p">.</span><span class="nn">Data</span><span class="p">.</span><span class="nc">IsOrderPlaced</span> <span class="p">&amp;&amp;</span> <span class="n">this</span><span class="p">.</span><span class="nn">Data</span><span class="p">.</span><span class="nc">IsOrderBilled</span> <span class="k">with</span>
        <span class="p">|</span> <span class="bp">true</span> <span class="p">-&gt;</span>
            <span class="n">this</span><span class="p">.</span><span class="nc">MarkAsComplete</span><span class="bp">()</span>
            <span class="k">let</span> <span class="n">shipOrder</span> <span class="p">=</span> <span class="k">new</span> <span class="nc">ShipOrder</span><span class="p">(</span><span class="n">this</span><span class="p">.</span><span class="nn">Data</span><span class="p">.</span><span class="nc">OrderId</span><span class="p">)</span>
            <span class="n">context</span><span class="p">.</span><span class="nc">SendLocal</span><span class="p">(</span><span class="n">shipOrder</span><span class="p">)</span>
        <span class="p">|</span> <span class="bp">false</span> <span class="p">-&gt;</span>
            <span class="nn">Task</span><span class="p">.</span><span class="nc">CompletedTask</span></code></pre></figure>

<p>Do implementacji <strong>NServiceBus Saga</strong>, z wykorzystaniem nowych możliwości języka <strong>F#</strong>, wystarczy jedna klasa, dziedzicząca dwa razy po tym samym interfejsie <code class="language-plaintext highlighter-rouge">IAmStartedByMessages</code>, który różni się tylko parametrem generycznym:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">IAmStartedByMessages&lt;OrderPlaced&gt;</code> - przetwarzanie wiadomości typu <code class="language-plaintext highlighter-rouge">OrderPlaced</code></li>
  <li><code class="language-plaintext highlighter-rouge">IAmStartedByMessages&lt;OrderBilled&gt;</code> - przetwarzanie wiadomości typu <code class="language-plaintext highlighter-rouge">OrderBilled</code></li>
</ul>

<p>Ponieważ całość może znajdować się w jednej klasie, możemy uprościć kod, przenosząc logikę działania funkcji <code class="language-plaintext highlighter-rouge">canShipOrder</code> oraz <code class="language-plaintext highlighter-rouge">shipOrder</code> do metody <code class="language-plaintext highlighter-rouge">ProcessOrder</code>, która znajduje się w samej klasie.</p>

<p><strong>F# 5</strong> wprowadza wiele innych przydatnych nowości, natomiast z punktu widzenia implementacji <strong>Sagi</strong> zmiana opisana w tym artykule jest najistotniejsza. Jeśli <strong>NServiceBus Saga</strong> jest <a href="/2020/09/29/adsd-nservicebus-csharp-fsharp-lets-dance-design.html">ważną częścią</a> zaprojektowanych rozwiązań, to możliwość dziedziczenia po tym samym interfejsie różniącym się parametrem generycznym zachęca do kodowania całych rozwiązań w języku <strong>F#</strong>. Daje to ciekawą perspektywę na przyszłość :)</p>

<p>Tymczasem udanego oglądania materiałów z nadchodzącej <a href="https://devblogs.microsoft.com/dotnet/net-5-0-launches-at-net-conf-november-10-12/" title="net 5 launches at net conf november 10-12">konferencji</a>!</p>

<p>=</p>

<h3> </h3>]]></content><author><name>mikedevbo</name></author><summary type="html"><![CDATA[Sprawdź, w jaki sposób wykorzystać nowe właściwości języka F# do implementacji NServiceBus Saga.]]></summary></entry></feed>