<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Victor Hogemann</title>
    <description>The latest articles on DEV Community by Victor Hogemann (@vhogemann).</description>
    <link>https://dev.to/vhogemann</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F992304%2Fad9b5209-751f-4c9d-b2c1-65af6c6f2b09.jpeg</url>
      <title>DEV Community: Victor Hogemann</title>
      <link>https://dev.to/vhogemann</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vhogemann"/>
    <language>en</language>
    <item>
      <title>Rinha de Backend 2024 - F#</title>
      <dc:creator>Victor Hogemann</dc:creator>
      <pubDate>Tue, 27 Feb 2024 11:05:01 +0000</pubDate>
      <link>https://dev.to/vhogemann/rinha-de-backend-2024-f-517</link>
      <guid>https://dev.to/vhogemann/rinha-de-backend-2024-f-517</guid>
      <description>&lt;p&gt;A &lt;a href="https://github.com/zanfranceschi/rinha-de-backend-2024-q1"&gt;Rinha de Backend&lt;/a&gt; é um evento organizado pelo &lt;a href="https://linktr.ee/zanfranceschi"&gt;Francisco Zanfrancheschi&lt;/a&gt;. As regras são simples, você precisa criar uma API rodando em docker compose, seguindo a arquitetura mínima pedida, e que sobreviva a um teste de carga previamente escrito.&lt;/p&gt;

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

&lt;p&gt;Eu novamente estou participando, e novamente estou fazendo em F#. O repositório com o projeto está no GitHub:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/vhogemann/rinha-de-backend-2024-fsharp"&gt;Rinha de Backend 2024 - FSharp&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Crébito
&lt;/h1&gt;

&lt;p&gt;O tema desse ano foi controle de concorrência, criar uma API com dois endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Saldo e extrato &lt;code&gt;GET /clientes/{id}/extrato&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Débito e crédito &lt;code&gt;POST /clientes/{id}/transacoes&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tudo que você tem que fazer é garantir que a sua API, com no mínimo duas instâncias, garanta a consistência das transações sem deixar que o saldo do usuário estoure o limite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Garantindo a consistência
&lt;/h2&gt;

&lt;p&gt;O maior problema dessa ediçao da Rinha é garantir que as transações sejam consistentes. O esquema mínimo da persistência pede por duas entidades: &lt;code&gt;Saldo&lt;/code&gt; e &lt;code&gt;Transação&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Saldo é o valor atual da conta do cliente, e transação é a lista de operações de débito e crédito aplicadas a conta.&lt;/p&gt;

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

&lt;p&gt;Precisamos garantir duas coisas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Que o &lt;code&gt;saldo - limite&lt;/code&gt; nunca seja &lt;strong&gt;negativo&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Que transações inválidas não sejam salvas na tabela&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tudo isso enquanto garantimos que a performance da API seja a melhor possível.&lt;/p&gt;

&lt;p&gt;Escolhi usar &lt;a href="https://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt; como banco de dados. O DDL completo está &lt;a href="https://github.com/vhogemann/rinha-de-backend-2024-fsharp/blob/main/src/bootstrap.sql"&gt;aqui&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Saldo
&lt;/h3&gt;

&lt;p&gt;Eu não sou nenhum mago do SQL, então a estragégia aqui é a mais simples possível. Uma &lt;code&gt;constraint&lt;/code&gt; do tipo &lt;code&gt;CHECK&lt;/code&gt; que garante que o saldo nunca seja negativo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- UNLOGGED TABLE é uma tabela que não é escrita no WAL, o que&lt;/span&gt;
&lt;span class="c1"&gt;-- significa que não pode participar de transações.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;UNLOGGED&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;overdraft_limit&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="c1"&gt;-- Garante que o "saldo - limite" nunca seja negativo&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;amount_overdraft_limit_check&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;overdraft_limit&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;balance_client_id_id_key&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;balance_client_id_idx&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Transação
&lt;/h3&gt;

&lt;p&gt;As duas tabelas que eu tenho, &lt;code&gt;balance&lt;/code&gt; e &lt;code&gt;transactions&lt;/code&gt;, são criadas como &lt;code&gt;UNLOGGED TABLE&lt;/code&gt;. Isso aumenta a performance já que o PostgreSQL não precisa escrever essas tabelas no &lt;code&gt;WAL&lt;/code&gt; (Write Ahead Log), que é um arquivo de log que o PostgreSQL usa pra garantir a consistência dos dados.&lt;/p&gt;

&lt;p&gt;A desvantagem é que essas tabelas não podem participar de transações, então pra garantir a integrigade entre as duas a &lt;strong&gt;ordem&lt;/strong&gt; das operações é importante.&lt;/p&gt;

&lt;p&gt;Outra coisa que ajuda a performance é agrupar as operações de atualizar o saldo e salvar a transação em uma única chamada. Isso também é crítico pra garantir a consistência dos dados.&lt;/p&gt;

&lt;p&gt;Pra isso, as duas operações são feitas dentro de uma &lt;code&gt;STORED PROCEDURE&lt;/code&gt; que é chamada pela API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;PROCEDURE&lt;/span&gt; &lt;span class="n"&gt;withdrawal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a_client_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w_amount&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w_description&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
&lt;span class="c1"&gt;-- Se a constraint for violada, a PROC para aqui e o saldo não é&lt;/span&gt;
&lt;span class="c1"&gt;-- atualizado, nem a transação é salva&lt;/span&gt;
    &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt;
    &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;w_amount&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;client_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a_client_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;transactions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a_client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'WITHDRAWAL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;w_description&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caso a chamada pra &lt;code&gt;withdrawal&lt;/code&gt; falhe, eu capturo a exceção e retorno &lt;code&gt;422&lt;/code&gt; na API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minha implementação em F
&lt;/h2&gt;

&lt;p&gt;Assim como eu fiz na &lt;a href="https://github.com/vhogemann/rinha-de-backend-2023"&gt;minha última participação&lt;/a&gt; na &lt;a href="https://github.com/zanfranceschi/rinha-de-backend-2023-q3"&gt;Rinha de 2023&lt;/a&gt;, escolhi fazer tudo em &lt;a href="https://fsharp.org"&gt;F#&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;F# é uma linguagem funcional, multi-paradigma, que roda em cima do Dotnet CLR. Ela foi criada por &lt;a href="https://mastodon.sdf.org/@dsyme"&gt;Don Syme&lt;/a&gt; na Microsoft, e pertence a família &lt;a href="https://en.wikipedia.org/wiki/ML_(programming_language)"&gt;ML&lt;/a&gt; de linguagens de programação funcionais, assim como o &lt;a href="https://ocaml.org/"&gt;OCaml&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Esse artigo tem um bom &lt;a href="https://jkone27-3876.medium.com/comparing-ocaml-to-f-f75e4ab27769"&gt;resumo das diferenças entre F# e OCaml&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Um dos méritos do F# é ser bem expressivo, e bastante enxuto, então &lt;a href="https://github.com/vhogemann/rinha-de-backend-2024-fsharp/blob/main/src/Rinha/Program.fs"&gt;a implementação da API&lt;/a&gt; inteira ficou em menos de 200 linhas de código. O que eu vou fazer aqui nesse aquivo é um code-review de cada módulo como forma de apresentar o F# pra vocês.&lt;/p&gt;

&lt;p&gt;Eu vou omitir algums imports pra facitar a leitura, e incluir comentários que não estão no fonte, mas a maior parte do código vai estar aqui.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modelo
&lt;/h3&gt;

&lt;p&gt;Aqui eu defino os DTOs que a aplicação vai usar. Como mais na frente você vai ver que eu estou usando SQL direto, não preciso me preocupar em separar o que vai pra View e o que vai pro Banco.&lt;/p&gt;

&lt;p&gt;Uma coisa que vale a pena explicar aqui sobre F# é que ele tem o conceito de &lt;strong&gt;módulos&lt;/strong&gt;. Módulos são usados pra organizar &lt;em&gt;funções&lt;/em&gt;, &lt;em&gt;tipos&lt;/em&gt; e &lt;em&gt;valores&lt;/em&gt; relacionados.&lt;/p&gt;

&lt;p&gt;Módulos são diferentes de &lt;strong&gt;namespaces&lt;/strong&gt;, que também existem em C#, porquê esses não suportam funçoes nem valores, só declaracão de tipos ou &lt;strong&gt;módulos&lt;/strong&gt;. Um módulo funciona mais ou menos como uma classe estática, onde tudo que é declarado vira uma propriedade pública.&lt;/p&gt;

&lt;p&gt;Os &lt;strong&gt;DTOs&lt;/strong&gt; declarados nesse módulo são &lt;em&gt;records&lt;/em&gt;. Em F# records diferem de classes por:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Imutáveis por padrão, uma vez criados não podem ser mudados&lt;/li&gt;
&lt;li&gt;Tem igualdade estrutural, dois &lt;em&gt;record&lt;/em&gt; são iguais se todas as propriedades forem iguais&lt;/li&gt;
&lt;li&gt;Pode ser usados em &lt;em&gt;pattern matching&lt;/em&gt; pra desmembrar e comparar todos os seus campos
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c1"&gt;// F# é compatível com C#, então podemos usar o pacote&lt;/span&gt;
    &lt;span class="c1"&gt;// System.Text.JSON padrão do Dotnet pra serialização&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JsonSerializerOptions&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;// Configuração da serialização em JSON pra usar `snake_case`&lt;/span&gt;
    &lt;span class="c1"&gt;// esse objeto `options` vai ser usado lá na frente pelos&lt;/span&gt;
    &lt;span class="c1"&gt;// controllers&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PropertyNamingPolicy&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="nn"&gt;JsonNamingPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SnakeCaseLower&lt;/span&gt;

    &lt;span class="c1"&gt;// View object pra receber as requisições de débito/crédito&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;TransacaoRequest&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
          &lt;span class="n"&gt;tipo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
          &lt;span class="n"&gt;descricao&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Resposta pra uma transação de débito/crédito&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;TransacaoResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;limite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;saldo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Os tipos list, array, map, option e outros podem ser&lt;/span&gt;
    &lt;span class="c1"&gt;// declarados como &amp;lt;Tipo do Item&amp;gt; list&lt;/span&gt;
    &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ExtratoResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;saldo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ExtratoSaldoResponse&lt;/span&gt;
          &lt;span class="n"&gt;ultimasTransacoes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ExtratoTransacaoResponse&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Em F# você precisa declarar os tipos antes de poder&lt;/span&gt;
    &lt;span class="c1"&gt;// referenciar eles. Mas você pode usar `and` pra&lt;/span&gt;
    &lt;span class="c1"&gt;// encadear as declarações e ajudar um pouco na hora&lt;/span&gt;
    &lt;span class="c1"&gt;// de ler o código.       &lt;/span&gt;
    &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="nc"&gt;ExtratoSaldoResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;limite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
          &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
          &lt;span class="n"&gt;dataExtrato&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateTime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="nc"&gt;ExtratoTransacaoResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
          &lt;span class="n"&gt;tipo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
          &lt;span class="n"&gt;descricao&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
          &lt;span class="n"&gt;realizadaEm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DateTime&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Persistência
&lt;/h3&gt;

&lt;p&gt;Eu estou usando &lt;a href="https://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt; como banco de dados, e um wrapper F# para &lt;a href="https://learn.microsoft.com/en-Us/dotnet/framework/data/adonet/"&gt;ADO.Net&lt;/a&gt; chamado &lt;a href="https://github.com/pimbrouwers/Donald"&gt;Donald&lt;/a&gt; que oferece uma API funcional em cima da API normal em C#.&lt;/p&gt;

&lt;p&gt;A primeira coisa é declarar um novo módulo &lt;code&gt;Persistence&lt;/code&gt;, e declarar algumas funções que vão receber um &lt;code&gt;IDataReader&lt;/code&gt;, que é um helper pra ler os dados do &lt;code&gt;resultset&lt;/code&gt; que vem do banco e retornar um dos DTOs declarados em &lt;code&gt;Model&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Persistence&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;Donald&lt;/span&gt;
    &lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="c1"&gt;// Modulo onde declaramos os DTOs&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;transacaoResposneDataReader&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IDataReader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TransacaoResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
         &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;saldo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadInt32&lt;/span&gt; &lt;span class="s2"&gt;"amount"&lt;/span&gt; 
           &lt;span class="n"&gt;limite&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadInt32&lt;/span&gt; &lt;span class="s2"&gt;"overdraft_limit"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;balanceDataReader&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IDataReader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ExtratoSaldoResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadInt32&lt;/span&gt; &lt;span class="s2"&gt;"amount"&lt;/span&gt;
          &lt;span class="n"&gt;limite&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadInt32&lt;/span&gt; &lt;span class="s2"&gt;"overdraft_limit"&lt;/span&gt;
          &lt;span class="n"&gt;dataExtrato&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Now&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Pattern Matching para mapear o tipo da transação que vem do banco&lt;/span&gt;
    &lt;span class="c1"&gt;// como "DEPOSIT"/"WITHDRAWAL" e precisa ser retornada como "c"/"d" na API&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tipoMapper&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;function&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"DEPOSIT"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"c"&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"WITHDRAWAL"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"d"&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"?"&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;transactionDataReader&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IDataReader&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ExtratoTransacaoResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;valor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadInt32&lt;/span&gt; &lt;span class="s2"&gt;"amount"&lt;/span&gt;
          &lt;span class="n"&gt;tipo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadString&lt;/span&gt; &lt;span class="s2"&gt;"transaction_type"&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tipoMapper&lt;/span&gt;
          &lt;span class="n"&gt;descricao&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadString&lt;/span&gt; &lt;span class="s2"&gt;"description"&lt;/span&gt;
          &lt;span class="n"&gt;realizadaEm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ReadDateTime&lt;/span&gt; &lt;span class="s2"&gt;"transaction_date"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora eu declaro duas funções, &lt;code&gt;withdrawal&lt;/code&gt; pra débitos e &lt;code&gt;deposit&lt;/code&gt; pra créditos.&lt;/p&gt;

&lt;p&gt;Eu estou usando &lt;code&gt;Npgsql&lt;/code&gt; como driver pra PostgreSQL, e o &lt;code&gt;Donald&lt;/code&gt; tem um módulo &lt;code&gt;Db&lt;/code&gt; que oferece uma API funcional pra criar comandos, setar parâmetros, e executar queries.&lt;/p&gt;

&lt;p&gt;Em F# você pode usar o &lt;em&gt;forward pipe&lt;/em&gt; &lt;code&gt;|&amp;gt;&lt;/code&gt; pra passar o resultado de uma função como argumento pra outra, o que deixa o código mais legível.&lt;/p&gt;

&lt;p&gt;Um truque que eu usei aqui foi declarar mais de um statement SQL em cada query. Assim eu consigo fazer a transação de débito/crédito e já retornar o saldo atualizado em uma única chamada.&lt;/p&gt;

&lt;p&gt;A última chamada de cada função é pra &lt;code&gt;Db.Async.querySingle&lt;/code&gt;, que é uma função que executa a query e retorna um único resultado. O &lt;code&gt;transacaoResposneDataReader&lt;/code&gt; que eu declarei lá em cima é usado pra mapear o resultado do banco pra um dos DTOs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;withdrawal&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbconn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NpgsqlConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="s2"&gt;"CALL withdrawal(@clientId, @amount, @description);
             SELECT amount, overdraft_limit FROM balance WHERE client_id = @clientId;"&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"@clientId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlInt32&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt;
              &lt;span class="s2"&gt;"@amount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlInt32&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
              &lt;span class="s2"&gt;"@description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlString&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;dbconn&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newCommand&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParams&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;querySingle&lt;/span&gt; &lt;span class="n"&gt;transacaoResposneDataReader&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O controle de concorrência é feito pelo PostgreSQL, a &lt;code&gt;STORED PROCEDURE&lt;/code&gt; responsável pelo débito (&lt;code&gt;withdrawal&lt;/code&gt;) cria uma transação e depende de uma &lt;code&gt;CONSTRAINT&lt;/code&gt; do tipo &lt;code&gt;CHECK&lt;/code&gt; pra garantir que o saldo não fique negativo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;deposit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbconn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NpgsqlConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="s2"&gt;"CALL deposit(@clientId, @amount, @description);
             SELECT amount, overdraft_limit FROM balance WHERE client_id = @clientId;"&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"@clientId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlInt32&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt;
              &lt;span class="s2"&gt;"@amount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlInt32&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
              &lt;span class="s2"&gt;"@description"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlString&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;dbconn&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newCommand&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParams&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;querySingle&lt;/span&gt; &lt;span class="n"&gt;transacaoResposneDataReader&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finalmente eu declaro duas funções para pegar o saldo e as últimas transações do cliente, que eu combino no &lt;code&gt;Controller&lt;/code&gt; pra retornar o extrato.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getBalance&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbconn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NpgsqlConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SELECT amount, overdraft_limit FROM balance WHERE client_id = @clientId"&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"@clientId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlInt32&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;dbconn&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newCommand&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParams&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;querySingle&lt;/span&gt; &lt;span class="n"&gt;balanceDataReader&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getTransactions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dbconn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NpgsqlConnection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="s2"&gt;"""
            SELECT amount, transaction_type, description, transaction_date 
            FROM transactions 
            WHERE client_id = @clientId
            ORDER BY transaction_date DESC LIMIT 10
            """&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"@clientId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqlInt32&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;dbconn&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newCommand&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setParams&lt;/span&gt; &lt;span class="n"&gt;parameters&lt;/span&gt;
        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Async&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="n"&gt;transactionDataReader&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Controllers
&lt;/h3&gt;

&lt;p&gt;Pra implementar a API REST eu estou usando uma biblioteca chamada &lt;a href="https://www.falcoframework.com/"&gt;Falco&lt;/a&gt; que reutiliza componentes do &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core?view=aspnetcore-8.0"&gt;ASP.NET Core&lt;/a&gt; e oferece uma API funcional em F#.&lt;/p&gt;

&lt;p&gt;As duas primeiras funções que eu declaro utilizam dois tipos de &lt;a href="https://en.wikipedia.org/wiki/Monad_(functional_programming)"&gt;monad&lt;/a&gt;: &lt;code&gt;Option&lt;/code&gt; e &lt;code&gt;Result&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A forma preferida de lidar com &lt;code&gt;Null&lt;/code&gt; em F# é através do tipo &lt;code&gt;Option&lt;/code&gt;, que pode ter dois valores: &lt;code&gt;Some &amp;lt;T&amp;gt;&lt;/code&gt; e &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;E &lt;code&gt;Result&lt;/code&gt; é utilizado pra representar o resultado de alguma operação, também tendo dois valores: &lt;code&gt;Ok &amp;lt;T&amp;gt;&lt;/code&gt; e &lt;code&gt;Error &amp;lt;Exception&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;optionToResponse&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;'&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofJsonOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withStatusCode&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofEmpty&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;deserialize&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getJsonOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Ok&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Services.inject&lt;/code&gt; é uma função do &lt;code&gt;Falco&lt;/code&gt; que apresenta a funcionalidade de &lt;em&gt;dependency injection&lt;/em&gt; do ASP.NET Core de forma funcional. &lt;/p&gt;

&lt;p&gt;No caso abaixo, &lt;code&gt;Services.inject&amp;lt;NpsqlConnection&amp;gt;&lt;/code&gt; recebe como parâmetro uma função, cujo parâmetro &lt;code&gt;dbconn&lt;/code&gt; é injetado com uma instância da conexão com o banco de dados.&lt;/p&gt;

&lt;p&gt;A sintaxe &lt;code&gt;fun parametros -&amp;gt; ...&lt;/code&gt; é como você declara um &lt;code&gt;lambda&lt;/code&gt;, ou função anônima, em F#.&lt;/p&gt;

&lt;p&gt;O bloco &lt;code&gt;task { ... }&lt;/code&gt; é uma &lt;code&gt;computational expression&lt;/code&gt;, que é uma feature do F#. Expressões computacionais oferecem uma forma de abstrair detalhes de uma computação para que você possa se concentrar na lógica.&lt;/p&gt;

&lt;p&gt;Nesse caso a expressão &lt;code&gt;task { ... }&lt;/code&gt; retorna um &lt;code&gt;System.Threading.Task&lt;/code&gt; do Dotnet. É a versão do F# do &lt;code&gt;async/await&lt;/code&gt; do C#, sendo que o F# implementou esse conceito primeiro.&lt;/p&gt;

&lt;p&gt;Tem uma série de tutoriais muito bons que explica o funcionamento de expressões computacionais no site &lt;a href="https://fsharpforfunandprofit.com/series/computation-expressions/"&gt;F# For fun and profit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Abaixo a função que retorna o extrato:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nn"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NpgsqlConnection&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;dbconn&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="c1"&gt;// "task" é como se lida com async em F#&lt;/span&gt;
                &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getRoute&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="nc"&gt;GetInt&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
            &lt;span class="c1"&gt;// "let!" bloqueia até que o valor esteja disponível&lt;/span&gt;
                    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;mayBeSaldo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Persistence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getBalance&lt;/span&gt; &lt;span class="n"&gt;dbconn&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt;
                    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;transacoes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Persistence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getTransactions&lt;/span&gt; &lt;span class="n"&gt;dbconn&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt;
                        &lt;span class="n"&gt;mayBeSaldo&lt;/span&gt;
                        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;saldo&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;saldo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;saldo&lt;/span&gt;
                              &lt;span class="n"&gt;ultimasTransacoes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transacoes&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
                        &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;optionToResponse&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;|&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
                &lt;span class="o"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E aqui é a função que faz as transações, que é a mais complexa. Ela faz a validação do payload, e chama a função de débito ou crédito dependendo do tipo da transação.&lt;/p&gt;

&lt;p&gt;Uma coisa que provavélmente eu deveria melhorar aqui é que eu dependo da exceção pra retornar um erro &lt;code&gt;422&lt;/code&gt; caso a transação estoure o limite do cliente. Essa exceção vem lá do PostgreSQL na minha &lt;em&gt;STORED PROCEDURE&lt;/em&gt;, e existem formas de tratar o erro no próprio SQL e retornar um erro mais amigável.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nn"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;NpgsqlConnection&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;dbconn&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;clientId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getRoute&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="nc"&gt;GetInt&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt;
                    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deserialize&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
                    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
                    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withStatusCode&lt;/span&gt; &lt;span class="mi"&gt;422&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofPlainText&lt;/span&gt; &lt;span class="s2"&gt;"Bad Request"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
                    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Ok&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="c1"&gt;// Outro ponto que eu poderia melhorar...&lt;/span&gt;
                    &lt;span class="c1"&gt;// Tudo isso aqui poderia estar em uma função declarada lá em cima&lt;/span&gt;
                    &lt;span class="c1"&gt;// no módulo Model... ¯\_(ツ)_/¯&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descricao&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descricao&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descricao&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withStatusCode&lt;/span&gt; &lt;span class="mi"&gt;422&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofPlainText&lt;/span&gt; &lt;span class="s2"&gt;"Bad Request"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
                    &lt;span class="k"&gt;else&lt;/span&gt;
                    &lt;span class="c1"&gt;// GO HORSE PROGRAMMING&lt;/span&gt;
                    &lt;span class="c1"&gt;// Aqui eu trato a exception que pode vir do banco caso&lt;/span&gt;
                    &lt;span class="c1"&gt;// a constraint seja violada&lt;/span&gt;
                    &lt;span class="k"&gt;try&lt;/span&gt;
                        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                        &lt;span class="c1"&gt;// Pattern matching!&lt;/span&gt;
                            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tipo&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
                            &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"c"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Persistence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt; &lt;span class="n"&gt;dbconn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descricao&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Credito&lt;/span&gt;
                            &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"d"&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Persistence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withdrawal&lt;/span&gt; &lt;span class="n"&gt;dbconn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;descricao&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Debito&lt;/span&gt;
                            &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;failwith&lt;/span&gt; &lt;span class="s2"&gt;"Invalid transaction type"&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;optionToResponse&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;|&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
                    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="p"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withStatusCode&lt;/span&gt; &lt;span class="mi"&gt;422&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ofEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;
                &lt;span class="o"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Routing
&lt;/h3&gt;

&lt;p&gt;Finalmente o ponto de entrada do programa. Aqui o &lt;code&gt;Falco&lt;/code&gt; oferece uma API funcional pra configurar o servidor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// "EntryPoint" diz ao compilador que essa função é o ponto de entrada do aplicativo&lt;/span&gt;
&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;EntryPoint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="c1"&gt;// Função "main" semelhante a C/C++/C#/Java&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetEnvironmentVariable&lt;/span&gt; &lt;span class="s2"&gt;"ASPNETCORE_ENVIRONMENT"&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt; &lt;span class="o"&gt;[||]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;required_json&lt;/span&gt; &lt;span class="s2"&gt;"appsettings.json"&lt;/span&gt;
        &lt;span class="n"&gt;optional_json&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"appsettings.{env}.json"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;webHost&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Configura a injeção de dependência, adicionando a conexão com o banco&lt;/span&gt;
        &lt;span class="n"&gt;add_service&lt;/span&gt; &lt;span class="o"&gt;(_.&lt;/span&gt;&lt;span class="nc"&gt;AddNpgsqlDataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Default"&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;

        &lt;span class="c1"&gt;// Configura os endpoints&lt;/span&gt;
        &lt;span class="c1"&gt;// Controller.transaction e Controller.balance foram declaradas no modulo&lt;/span&gt;
        &lt;span class="c1"&gt;// Controller lá em cima&lt;/span&gt;
        &lt;span class="n"&gt;endpoints&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="s2"&gt;"/clientes/{id}/transacoes"&lt;/span&gt; &lt;span class="nn"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt;
              &lt;span class="n"&gt;get&lt;/span&gt; &lt;span class="s2"&gt;"/clientes/{id}/extrato"&lt;/span&gt; &lt;span class="nn"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;// A função main precisa retornar um inteiro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Testes
&lt;/h1&gt;

&lt;p&gt;Abaixo os resultados do teste de carga. Eu acho que a performance está excelente pra um projeto onde eu coloquei muito pouco esforço em otimizar qualquer coisa.&lt;/p&gt;

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

&lt;p&gt;Os resultados completos você pode ver &lt;a href="https://vhogemann.github.io/assets/rinha-2024-q1/gatling/index.html"&gt;aqui&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusão
&lt;/h1&gt;

&lt;p&gt;Se você chegou até aqui, muito obrigado! Espero que você tenha gostado de conhecer um pouco mais sobre F#. É minha linguagem de programação favorita, e a melhor linguagem da qual você nunca ouviu falar!&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Testing Native Queries with Functions in Spring Boot with JPA and H2</title>
      <dc:creator>Victor Hogemann</dc:creator>
      <pubDate>Thu, 31 Aug 2023 09:17:59 +0000</pubDate>
      <link>https://dev.to/vhogemann/testing-native-queries-with-functions-in-spring-boot-with-jpa-and-h2-4f5n</link>
      <guid>https://dev.to/vhogemann/testing-native-queries-with-functions-in-spring-boot-with-jpa-and-h2-4f5n</guid>
      <description>&lt;p&gt;Spring Boot gives you the facility of testing your repositories using H2, an embedded in-memory database that can emulate other database's dialects. It's a handy feature, but with limitations. One of these limitations is the fact that H2 doesn't emulate the full range of existing functions in other databases.&lt;br&gt;
Then, say you find yourself wanting to execute the query bellow on Microsoft SQL Server using JPA native query functionality, and map the projection to a POJO:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- MS SQL Server has a FORMAT function for dates, H2 doesn't
SELECT 
  FORMAT(tbl.TIMESTAMP, 'yyyy-MM-dd') as date, 
  count(*) as count,
  tbl.COUNTRY as value
FROM
  ORDERS tbl
GROUP BY
  FORMAT(tbl.TIMESTAMP, 'yyyy-MM-dd'), tbl.COUNTRY;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run just fine on SQL Server, but it will fail on H2 because the function FORMAT doesn't exist there. The usual solution would be to either point your tests to a real SQL Server instance, not ideal if you're running it on a CI server or if you want to keep the external dependencies to a minimum, or you just skip testing it, again not the best solution.&lt;br&gt;
But there's a third option, H2 lets you define your own functions, using Java code. And Spring Boot will happily execute any script you put in the &lt;code&gt;src/test/resources/data.sql&lt;/code&gt; file before running your tests.&lt;br&gt;
The script bellows shows a very simple implementation of the FORMAT function, that emulates just enough the native SQL Server version that's enough to run my unit tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- src/test/resources/data.sql
create alias FORMAT as $$
import java.util.Date;
import java.text.SimpleDateFormat;
@CODE
String format(Date date, String format) {
    return new SimpleDateFormat(format).format(date);
}
$$;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hope this can save somebody else a few hours of internet searching, and fumbling around with Spring Boot and H2 documentation.&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>h2</category>
      <category>testing</category>
    </item>
    <item>
      <title>Using database functions in JPA Criteria projections</title>
      <dc:creator>Victor Hogemann</dc:creator>
      <pubDate>Tue, 15 Aug 2023 20:39:46 +0000</pubDate>
      <link>https://dev.to/vhogemann/using-database-functions-in-jpa-criteria-projections-4aih</link>
      <guid>https://dev.to/vhogemann/using-database-functions-in-jpa-criteria-projections-4aih</guid>
      <description>&lt;p&gt;JPA and Hibernate are great if you don’t step too much outside the regular mapping-tables-to-objects business, but if you want to do a bit more with your database using the Criteria API, you might find some gotchas.&lt;/p&gt;

&lt;p&gt;For example, I was trying to map the following query to a projection, and failing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT
    FORMAT(timestamp, ''yyyy-DD-mm'') as day,
    COUNT(value) as count,
    value as value
FROM
    table
GROUP BY
    FORMAT(timestamp, ''yyyy-MM-dd''), value;
The java bit of the projection was like this:

Query query = cb.multiselect(
    cb.function("format", String.class, Table_.timestamp, cb.literal("yyyy-MM-dd"),
    cb.count(Table_.value),
    root. Get(Table_.value),
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, if you’re targeting MS SQL Server, executing this will trigger an exception looking like the one bellow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;java.lang.NullPointerException at org.hibernate.hql.internal.ast.tree.ConstructorNode.formatMissingContructorExceptionMessage(ConstructorNode.java:192)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not very helpful, but what’s actually happening here is: Hibernate doesn’t know about the FORMAT function in MS SQL Server. So, the way it get to learn about the SQL syntax of a given database is through a dialect class. There are many dialects to choose from when dealing with MS SQL Server, but none has the FORMAT function registered!&lt;/p&gt;

&lt;p&gt;How do I know? Well, I looked at the source code… really. I invite you to go ahead and do the same, they’re really simple to understand, they’re here.&lt;/p&gt;

&lt;p&gt;So, in the end, the solution was to register the FORMAT by extending the dialect I was using, SQLServer2012Dialect, and register the function myself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class ExtendedSQLServerDialect extends SQLServer2012Dialect {
public ExtendedSQLServerDialect() {
        registerFunction("format",   new SQLFunctionTemplate( StandardBasicTypes.STRING, "FORMAT(?1, ?2)"));
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use it in your application configuration, instead of the one provided Hibernate, and that should solve the problem.&lt;/p&gt;

</description>
      <category>java</category>
    </item>
  </channel>
</rss>
