<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: 👨‍💻 Lucas Silva</title>
    <description>The latest articles on Forem by 👨‍💻 Lucas Silva (@lukesilva).</description>
    <link>https://forem.com/lukesilva</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%2F230431%2F111ea1c7-9a00-4804-a54c-8cabdd88d1ec.jpeg</url>
      <title>Forem: 👨‍💻 Lucas Silva</title>
      <link>https://forem.com/lukesilva</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/lukesilva"/>
    <language>en</language>
    <item>
      <title>RabbitMQ: conceitos fundamentais</title>
      <dc:creator>👨‍💻 Lucas Silva</dc:creator>
      <pubDate>Sun, 12 Jan 2025 19:41:53 +0000</pubDate>
      <link>https://forem.com/lukesilva/rabbitmq-conceitos-fundamentais-9kp</link>
      <guid>https://forem.com/lukesilva/rabbitmq-conceitos-fundamentais-9kp</guid>
      <description>&lt;p&gt;O RabbitMQ é um dos message brokers mais populares, conhecido por sua flexibilidade e robustez no processamento de mensagens. Este artigo apresenta os principais conceitos do RabbitMQ, incluindo publishers, consumers, exchanges, filas e mecanismos de reconhecimento de mensagens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk8nuljmbh6sgc3lot1ec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk8nuljmbh6sgc3lot1ec.png" alt="Fluxo básico de um sistema de mensageria" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Publisher/Producer
&lt;/h2&gt;

&lt;p&gt;Uma aplicação &lt;strong&gt;publisher&lt;/strong&gt; é responsável por publicar ou produzir mensagens. Além disso, uma aplicação que publica mensagens também pode consumi-las.&lt;br&gt;&lt;br&gt;
No RabbitMQ, uma mensagem publicada precisa ser roteada para uma fila. Se houver consumidores online conectados à fila, a mensagem será enviada diretamente para eles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consumer
&lt;/h2&gt;

&lt;p&gt;Um &lt;strong&gt;consumer&lt;/strong&gt; é uma aplicação que consome mensagens de uma fila e realiza o reconhecimento (acknowledgement) dessas mensagens. Da mesma forma que um publisher pode consumir mensagens, um consumer também pode publicá-las.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exchanges
&lt;/h2&gt;

&lt;p&gt;No RabbitMQ, as mensagens não são enviadas diretamente para as filas. Os publishers enviam mensagens para um &lt;strong&gt;exchange&lt;/strong&gt;, que é responsável por rotear as mensagens para filas específicas, com base nas ligações (&lt;em&gt;bindings&lt;/em&gt;) e chaves de roteamento (&lt;em&gt;routing keys&lt;/em&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Tipos de Exchanges
&lt;/h3&gt;

&lt;p&gt;Os exchanges são categorizados de acordo com a forma como realizam o roteamento de mensagens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct&lt;/strong&gt;: As mensagens são roteadas para filas cuja ligação (&lt;em&gt;binding&lt;/em&gt;) coincida exatamente com a chave de roteamento (&lt;em&gt;routing key&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fanout&lt;/strong&gt;: Roteia mensagens para todas as filas que estejam conectadas a ele.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Topic&lt;/strong&gt;: Faz um &lt;em&gt;match&lt;/em&gt; com base em padrões (&lt;em&gt;wildcards&lt;/em&gt;) entre a chave de roteamento da mensagem e a chave de ligação das filas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiofrflnzwugjrhy3eksq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiofrflnzwugjrhy3eksq.png" alt="Exemplo de exchange baseada em tópicos" width="788" height="284"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Headers&lt;/strong&gt;: Roteia mensagens com base nos cabeçalhos especificados na mensagem.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  Queues
&lt;/h2&gt;

&lt;p&gt;As &lt;strong&gt;filas&lt;/strong&gt; no RabbitMQ são coleções ordenadas de mensagens, e elas seguem uma lógica de &lt;em&gt;First In, First Out&lt;/em&gt; (FIFO) para o enfileiramento e desenfileiramento. Cada mensagem é processada em ordem, garantindo consistência no consumo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Acknowledgement
&lt;/h2&gt;

&lt;p&gt;O &lt;strong&gt;acknowledgement&lt;/strong&gt; é o mecanismo de confirmação de que uma mensagem foi entregue e/ou processada pelo consumidor. Ele permite que o RabbitMQ saiba quando pode marcar uma mensagem como concluída ou quando deve reenviar a mensagem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delivery Acknowledgement
&lt;/h3&gt;

&lt;p&gt;Existem diferentes formas de reconhecimento no RabbitMQ, que determinam quando uma mensagem é considerada processada com sucesso:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reconhecimento Automático&lt;/strong&gt;: O RabbitMQ considera a mensagem processada assim que ela é entregue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconhecimento Manual&lt;/strong&gt;: Requer que o consumidor informe explicitamente o status da mensagem. Isso pode ser feito de forma positiva ou negativa:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;basic.ack&lt;/code&gt;: Usado para indicar que a mensagem foi processada com sucesso.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;basic.nack&lt;/code&gt;: Usado para indicar que o processamento falhou.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;basic.reject&lt;/code&gt;: Similar ao &lt;code&gt;basic.nack&lt;/code&gt;, mas com limitações (não suporta múltiplas mensagens ao mesmo tempo).&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Quando o reconhecimento negativo ocorre, a mensagem pode ser reenfileirada, roteada para um &lt;strong&gt;dead letter exchange&lt;/strong&gt;, ou descartada, dependendo da configuração do campo &lt;code&gt;requeue&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dead Letter
&lt;/h2&gt;

&lt;p&gt;Mensagens podem ser marcadas como &lt;strong&gt;dead-letter&lt;/strong&gt; e republicadas caso algum dos seguintes eventos ocorra:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A mensagem é negativamente reconhecida por um consumidor.&lt;/li&gt;
&lt;li&gt;A mensagem expira devido ao TTL (&lt;em&gt;Time To Live&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;A mensagem excede o limite de tamanho configurado.&lt;/li&gt;
&lt;li&gt;A mensagem ultrapassa o limite de tentativas de entrega.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Dead letters permitem lidar com falhas de forma estruturada, garantindo maior resiliência no sistema.&lt;/p&gt;




&lt;p&gt;Compreender esses conceitos é essencial para projetar sistemas de mensageria eficientes e confiáveis. O RabbitMQ oferece flexibilidade para adaptar-se a diversas arquiteturas, tornando-o uma ferramenta poderosa no ecossistema de aplicações distribuídas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referêcias
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rabbitmq.com/docs" rel="noopener noreferrer"&gt;Documentação do RabbitMQ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>backend</category>
      <category>systemdesign</category>
      <category>programming</category>
      <category>pubsub</category>
    </item>
    <item>
      <title>Formulários com React Hook Form + Zod</title>
      <dc:creator>👨‍💻 Lucas Silva</dc:creator>
      <pubDate>Thu, 07 Nov 2024 22:40:58 +0000</pubDate>
      <link>https://forem.com/lukesilva/formularios-com-react-hook-form-zod-5cip</link>
      <guid>https://forem.com/lukesilva/formularios-com-react-hook-form-zod-5cip</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Quando comecei a programar, precisava escrever bastante código em JavaScript puro para coletar dados de um formulário. De lá para cá, o desenvolvimento web evoluiu tanto que hoje temos bibliotecas que abstraem grande parte desse trabalho, facilitando a gestão dos dados dos formulários.&lt;/p&gt;

&lt;p&gt;Neste artigo, vou mostrar como utilizar o &lt;a href="https://www.react-hook-form.com/" rel="noopener noreferrer"&gt;React Hook Form&lt;/a&gt; para trabalhar com dados de formulários e &lt;a href="https://zod.dev/" rel="noopener noreferrer"&gt;zod&lt;/a&gt; para validar esses dados de forma eficiente e organizada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tô com pressa, me dê o código completo
&lt;/h2&gt;

&lt;p&gt;Tá na mão:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Label&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/ui/label&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/ui/input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/ui/button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Gamepad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Gamepad2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lucide-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useForm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-hook-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;zodResolver&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@hookform/resolvers/zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Toaster&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sonner&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signUpForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nome deve ter ao menos 2 caracteres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nome deve ter no máximo 50 caracteres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;E-mail deve ter no máximo 100 caracteres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Senha deve ter no máximo 100 caracteres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refine&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Senhas não conferem!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SignUpForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;signUpForm&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;formState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isSubmitting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useForm&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SignUpForm&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;zodResolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signUpForm&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSignup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SignUpForm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Conta criada com sucesso!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Toaster&lt;/span&gt; &lt;span class="na"&gt;richColors&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"min-h-screen lg:grid lg:grid-cols-2 antialiased gap-8"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"hidden lg:flex h-full justify-center border-r border-foreground/5 bg-foreground text-muted-foreground items-center gap-3 text-lg"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Gamepad2&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"font-semibold"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;gamers.shop&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col items-center justify-center gap-6 min-h-screen"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"px-10 w-96 h-full flex flex-col justify-center items-center lg:w-[500px]"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex items-center gap-2 mb-4 text-2xl font-semibold tracking-tight"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Gamepad&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
              Crie sua conta
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Gamepad&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleSignup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-4 w-full "&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Nome&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm text-red-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                &lt;span class="si"&gt;{}&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm text-red-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Senha&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm text-red-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"confirm"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Confirme a senha&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"confirm"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confirm&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm text-red-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-full"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isSubmitting&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                Criar conta
              &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Estruturando o Formulário
&lt;/h3&gt;

&lt;p&gt;Primeiro, criei um formulário com quatro campos: nome, e-mail, senha e confirmação de senha. Para facilitar o desenvolvimento da interface, utilizei &lt;code&gt;shadcn&lt;/code&gt;, &lt;code&gt;tailwind&lt;/code&gt; e &lt;code&gt;lucide-react&lt;/code&gt;. O uso de classes CSS pode parecer um pouco detalhado, mas elas ajudam a manter um design consistente.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-4 w-full "&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Nome&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Digite o seu nome"&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Digite o seu e-mail"&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Senha&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Digite uma senha"&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"confirm"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Confirme a senha&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"confirm"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Digite a senha novamente"&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-full"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    Criar conta
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Criei 4 campos nesse form: nome, e-mail, password e confirm. Preciso validá-los de alguma forma. Como esse exemplo server para explicar o uso de zod, evitei utilizar as propriedades nativas do HTML (required, maxlength etc).&lt;/p&gt;

&lt;h3&gt;
  
  
  Validação de Dados com Zod
&lt;/h3&gt;

&lt;p&gt;Para garantir que os dados inseridos nos campos estão corretos, criei um schema de validação com o &lt;code&gt;zod&lt;/code&gt;. O schema define as restrições para cada campo e personaliza as mensagens de erro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signUpFormSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nome deve ter ao menos 2 caracteres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nome deve ter no máximo 50 caracteres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;E-mail deve ter no máximo 100 caracteres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Senha deve ter no máximo 100 caracteres&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refine&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Senhas não conferem!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Esse schema define os tipos e as validações necessárias para cada campo. O método &lt;code&gt;.refine()&lt;/code&gt; foi utilizado para garantir que as senhas digitadas nos campos "password" e "confirm" são iguais.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refine&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Senhas não conferem!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;confirm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Integração com React Hook Form e Zod Resolver
&lt;/h3&gt;

&lt;p&gt;O &lt;code&gt;React Hook Form&lt;/code&gt; é uma biblioteca de formulários que melhora o desempenho ao reduzir re-renderizações desnecessárias e simplificar a manipulação de dados. Usei o &lt;code&gt;useForm()&lt;/code&gt; para configurar o formulário, passando o schema de validação por meio do &lt;code&gt;zodResolver&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;zodResolver&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@hookform/resolvers/zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useForm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-hook-form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SignUpForm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;signUpFormSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;formState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isSubmitting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useForm&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SignUpForm&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;zodResolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signUpFormSchema&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As funções e variáveis que utilizei de &lt;code&gt;useForm()&lt;/code&gt; são:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;handleSubmit&lt;/code&gt;: lida com o envio do formulário.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;register&lt;/code&gt;: vincula campos do formulário ao gerenciamento de dados do React Hook Form.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reset&lt;/code&gt;: redefine o formulário.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;isSubmitting&lt;/code&gt;: indica se o formulário está sendo enviado.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;errors&lt;/code&gt;: armazena erros de validação de cada campo.
### Função de Envio do Formulário
Para simular o envio do formulário, criei a função &lt;code&gt;handleSignup&lt;/code&gt;, que adiciona um tempo de espera para visualizar o estado &lt;code&gt;isSubmitting&lt;/code&gt; e exibe uma mensagem de sucesso com o &lt;code&gt;toast&lt;/code&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSignup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SignUpForm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Conta criada com sucesso!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois adicionei a função ao form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handleSignup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-4 w-full"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E por fim, utilizei o register para denominar qual campo pertence a qual propriedade, e mostrei os erros (caso eles existam):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt; &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Nome&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Digite o seu nome"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-sm text-red-500"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O resultado ficou assim:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foipm2sov0dn33pnulzva.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foipm2sov0dn33pnulzva.png" alt="Tela de criação de conta feita neste tutorial" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerações Finais
&lt;/h2&gt;

&lt;p&gt;Nesse texto, mostrei uma forma simples de integrar &lt;code&gt;React Hook Form&lt;/code&gt; e &lt;code&gt;zod&lt;/code&gt; para validação de formulários não controlados. A biblioteca também funciona com componentes controlados, então consulte a &lt;a href="https://www.react-hook-form.com/get-started/" rel="noopener noreferrer"&gt;documentação&lt;/a&gt; para explorar mais opções.&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
    </item>
    <item>
      <title>EF Core - Explosão Cartesiana</title>
      <dc:creator>👨‍💻 Lucas Silva</dc:creator>
      <pubDate>Thu, 03 Oct 2024 18:38:43 +0000</pubDate>
      <link>https://forem.com/lukesilva/ef-core-explosao-cartesiana-2lah</link>
      <guid>https://forem.com/lukesilva/ef-core-explosao-cartesiana-2lah</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Atuei em vários projetos que utilizavam EF Core, e em uma determinada ocasião, encontrei, junto ao meu time, um problema que não fazia muito sentido: uma query simples, com poucos Includes, em tabelas que não possuíam tanto registros (quando comparada a outras tabelas do mesmo banco), estourava timeout. Após análise, o time entendeu que o problema ocorria devido a um fenômeno chamado &lt;strong&gt;Explosão cartesiana&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que é?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;explosão cartesiana&lt;/strong&gt; ocorre quando, ao realizar queries com joins em propriedades de navegação de entidades no mesmo nível hierárquico em bancos relacionais, o banco de dados retorna um produto vetorial (cross product). Ou seja, cada linha de uma propriedade de navegação é combinada com cada linha da outra propriedade.&lt;/p&gt;

&lt;p&gt;Esse comportamento pode gerar um número inesperado de combinações entre os dados, aumentando drasticamente o volume de informações trafegadas entre o banco de dados e a aplicação. &lt;strong&gt;Consequentemente, isso pode afetar o desempenho da aplicação, aumentar os custos de infraestrutura e consumo de recursos, além de tornar a depuração e otimização mais complexa.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Como resolver?
&lt;/h2&gt;

&lt;p&gt;Uma das melhores formas de lidar com isso é utilizando queries separadas para carregar os dados envolvidos. O EF Core oferece uma solução simples para isso através do método &lt;code&gt;AsSplitQuery()&lt;/code&gt;, que carregará as coleções utilizando múltiplas queries, em vez de uma única query.&lt;/p&gt;

&lt;p&gt;Os exemplos abaixo foram feitos com uma coleção de 100000 blogs armazenados em uma instância de SQL Server 2022, rodando através de um contêiner Docker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exemplo sem AsSplitQuery
&lt;/h3&gt;

&lt;p&gt;Código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var withoutSplitQuery = context.Blogs
    .Include(b =&amp;gt; b.Posts)
    .Include(b =&amp;gt; b.BlogContributors)
        .ThenInclude(bc =&amp;gt; bc.Contributor)
    .ToList();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesse exemplo, cada Blog possui uma coleção de Posts e uma de BlogContributors. Como ambas estão no mesmo nível, cada post será combinado com cada contribuinte, o que resulta em um número de linhas maior do que o necessário.&lt;/p&gt;

&lt;p&gt;Query gerada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Content], [p].[Title], [t].[BlogId], [t].[ContributorId], [t].[ContributorId0], [t].[Name]
      FROM [Blogs] AS [b]
      LEFT JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]
      LEFT JOIN (
          SELECT [b0].[BlogId], [b0].[ContributorId], [c].[ContributorId] AS [ContributorId0], [c].[Name]
          FROM [BlogContributors] AS [b0]
          INNER JOIN [Contributors] AS [c] ON [b0].[ContributorId] = [c].[ContributorId]
      ) AS [t] ON [b].[BlogId] = [t].[BlogId]
      ORDER BY [b].[BlogId], [p].[PostId], [t].[BlogId], [t].[ContributorId]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resultado na busca de 100000 registros:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*************************************************************************
Tempo de execução sem splitquery: 4526ms
Número de blogs retornados: 100000
*************************************************************************
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exemplo com AsSplitQuery
&lt;/h3&gt;

&lt;p&gt;Código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var withSplitQuery = context.Blogs
    .Include(b =&amp;gt; b.Posts)
    .Include(b =&amp;gt; b.BlogContributors)
        .ThenInclude(bc =&amp;gt; bc.Contributor)
    .AsSplitQuery()
    .ToList();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Queries geradas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT [b].[BlogId], [b].[Name]
      FROM [Blogs] AS [b]
      ORDER BY [b].[BlogId]

SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[Title], [b].[BlogId]
      FROM [Blogs] AS [b]
      INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]
      ORDER BY [b].[BlogId]

SELECT [t].[BlogId], [t].[ContributorId], [t].[ContributorId0], [t].[Name], [b].[BlogId]
      FROM [Blogs] AS [b]
      INNER JOIN (
          SELECT [b0].[BlogId], [b0].[ContributorId], [c].[ContributorId] AS [ContributorId0], [c].[Name]
          FROM [BlogContributors] AS [b0]
          INNER JOIN [Contributors] AS [c] ON [b0].[ContributorId] = [c].[ContributorId]
      ) AS [t] ON [b].[BlogId] = [t].[BlogId]
      ORDER BY [b].[BlogId]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resultado na busca de 100000 registros:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*************************************************************************
Tempo de execução com splitquery: 3664ms
Número de blogs retornados: 100000
*************************************************************************
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por mais que os exemplos acima tenham utilizado um número relativamente pequeno de dados, os resultados de 10 execuções do teste com dados diferentes foram consistentes: as buscas com queries separadas foram mais rápidas que as buscas com queries únicas. Inclusive, o próprio EF lança um erro ao executar uma query que possa causar uma explosão cartesiana:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compiling a query which loads related collections for more than onecollection navigation, either via 'Include' or through projection, but no 'QuerySplittingBehavior' has been configured. By default, Entity Framework will use 'QuerySplittingBehavior.SingleQuery', which can potentially result in slow query performance.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Importante!&lt;/strong&gt;&lt;br&gt;
Há inúmeras variáveis que podem impactar na performance de uma query. Esse texto e os testes acima tratam exclusivamente sobre o problema de &lt;strong&gt;explosão cartesiana.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Configuração via DbContext
&lt;/h3&gt;

&lt;p&gt;O EF permite a configuração do comportamento como padrão. Para isso, basta configurar o DbContext:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;optionsBuilder
    .UseSqlServer("ConnectionString", o =&amp;gt;
    .UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fontes e recursos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.milanjovanovic.tech/blog/how-to-improve-performance-with-ef-core-query-splitting" rel="noopener noreferrer"&gt;How To Improve Performance With EF Core Query Splitting&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/pt-br/ef/core/querying/single-split-queries" rel="noopener noreferrer"&gt;Consultas únicas vs. consultas divididas&lt;/a&gt;;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>database</category>
      <category>performance</category>
    </item>
    <item>
      <title>TestContainers for integration testing .Net applications</title>
      <dc:creator>👨‍💻 Lucas Silva</dc:creator>
      <pubDate>Tue, 10 Sep 2024 13:37:57 +0000</pubDate>
      <link>https://forem.com/lukesilva/testcontainers-for-integration-testing-net-applications-2jhk</link>
      <guid>https://forem.com/lukesilva/testcontainers-for-integration-testing-net-applications-2jhk</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Unlike unit tests, integration tests allow validating the behavior of an application when all its components are used together. This includes databases, cache services, messaging services, etc.&lt;/p&gt;

&lt;p&gt;In theory, everything seems interesting and simple. But these tests can generate and alter a large volume of data, so it is necessary to be careful with the resources used. After all, accidents happen, and  you might end up executing a DELETE without WHERE, leading to the total deletion of a table. 😅&lt;/p&gt;

&lt;p&gt;To avoid these kinds of problems, it's possible to create these resources using Docker containers through the &lt;a href="https://testcontainers.com/" rel="noopener noreferrer"&gt;TestContainers library&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this tutorial, I'll explain the steps for using these containers in a .NET API.&lt;/p&gt;

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

&lt;p&gt;The complete project can be found at &lt;a href="https://github.com/madebyluque/TutorialIntegrationTests" rel="noopener noreferrer"&gt;this link&lt;/a&gt;. It's a task management API (the famous "To-Do list"). It consists basically of three parts:&lt;/p&gt;

&lt;p&gt;An entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace IntegrationTestingDemo.API;

public class Todo
{
    public Todo()
    {
    }

    public Todo(string title, string description)
    {
        Title = title;
        Description = description;
        Id = Guid.NewGuid().ToString().Replace("-", "");
        CreatedAt = DateTime.UtcNow;
        Done = false;
    }

    public string Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public bool Done { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? CompletedAt { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A service (for simplicity's sake, some operations were not created):&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 TodoService(TodoContext context) : ITodoService
{
    private readonly TodoContext _context = context;

    public async Task&amp;lt;string&amp;gt; Create(string title, string description)
    {
        var todo = new Todo(title, description);
        await _context.AddAsync(todo);
        await _context.SaveChangesAsync();
        return todo.Id;
    }

    public async Task&amp;lt;List&amp;lt;Todo&amp;gt;&amp;gt; GetAll()
    {
        return await _context.Todos.OrderBy(x =&amp;gt; x.CreatedAt).ToListAsync();
    }

    public async Task&amp;lt;Todo&amp;gt; GetById(string id)
    {
        return await _context.Todos.FirstOrDefaultAsync(x =&amp;gt; x.Id == id);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ApiController]
[Route("[controller]")]
public class TodoController(ITodoService todoService) : ControllerBase
{
    private readonly ITodoService _todoService = todoService;

    [HttpPost]
    public async Task&amp;lt;IActionResult&amp;gt; Create([FromBody] CreateTodoModel model)
    {
        var result = await _todoService.Create(model.Title, model.Description);
        return CreatedAtRoute(nameof(GetById), routeValues: new { Id = result }, result);
    }

    [HttpGet]
    public async Task&amp;lt;IActionResult&amp;gt; GetAll()
    {
        var todos = await _todoService.GetAll();
        return Ok(todos);
    }

    [HttpGet("{id}", Name = "GetById")]
    public async Task&amp;lt;IActionResult&amp;gt; GetById(string id)
    {
        var todo = await _todoService.GetById(id);
        return Ok(todo);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The TestContainer configuration is done when creating the WebApplicationFactory for integration tests. For this tutorial, I decided to use PostgreSQL. Creating it can be done as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder().WithUsername("postgres").WithPassword("postgres").Build();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can change the username and password as desired. There is also the option to change other settings in the builder, such as the db name, host etc.&lt;/p&gt;

&lt;p&gt;You can obtain the container's connection string as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_postgres.GetConnectionString()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may be necessary to remove the dbContext from the application to add a new one with the connection string from the test container. If that's the case, you can do it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var context = services.FirstOrDefault(descriptor =&amp;gt; descriptor.ServiceType == typeof(TodoContext));
if (context != null)
{
    services.Remove(context);
    var options = services.Where(r =&amp;gt; (r.ServiceType == typeof(DbContextOptions))
      || (r.ServiceType.IsGenericType &amp;amp;&amp;amp; r.ServiceType.GetGenericTypeDefinition() == typeof(DbContextOptions&amp;lt;&amp;gt;))).ToArray();
    foreach (var option in options)
    {
        services.Remove(option);
    }
}

services.AddDbContext&amp;lt;TodoContext&amp;gt;(options =&amp;gt;
{
    options.UseNpgsql(_postgres.GetConnectionString());
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, it is interesting that your WebApplicationFactory class implements the IAsyncLifetime interface so that the created container is initialized/stopped.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public Task InitializeAsync()
{
    return _postgres.StartAsync();
}

public new Task DisposeAsync()
{
    return _postgres.StopAsync();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the setup done, it is already possible to create integration tests. In the test below, I've used TodoService to create a Todo, then checked if its data was saved as expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Fact]
public async Task Create_ShouldCreateTodoAndReturnItsId()
{
    // Act
    var result = await _todoService.Create(TestTitle, TestDescription);

    // Assert
    var todo = await _dbContext.Todos.FirstOrDefaultAsync(x =&amp;gt; x.Id == result);
    Assert.NotNull(todo);
    Assert.False(todo.Done);
    Assert.Equal(TestTitle, todo.Title);
    Assert.Equal(TestDescription, todo.Description);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GitHub actions
&lt;/h2&gt;

&lt;p&gt;It is possible to integrate the tests into a pipeline. &lt;a href="https://www.milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet" rel="noopener noreferrer"&gt;This post by Milan Jovanović&lt;/a&gt; shows how to integrate them into a Github Action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Run Tests 🚀

on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  run-tests:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '8.0.x'

      - name: Restore
        run: dotnet restore ./IntegrationTestingDemo.sln

      - name: Build
        run: dotnet build ./IntegrationTestingDemo.sln --no-restore

      - name: Test
        run: dotnet test ./IntegrationTestingDemo.sln --no-build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the end of the tutorial. I hope it has been enough to help you implement integration tests with TestContainers in your application.&lt;/p&gt;

&lt;p&gt;Until next time!&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>testing</category>
      <category>docker</category>
    </item>
    <item>
      <title>TestContainers para testes de integração com .Net</title>
      <dc:creator>👨‍💻 Lucas Silva</dc:creator>
      <pubDate>Tue, 10 Sep 2024 13:34:51 +0000</pubDate>
      <link>https://forem.com/lukesilva/testcontainers-para-testes-de-integracao-com-net-359k</link>
      <guid>https://forem.com/lukesilva/testcontainers-para-testes-de-integracao-com-net-359k</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Diferente de testes de unidade, os testes de integração permitem validar o comportamento de uma aplicação quando todos os componentes dela são utilizados em conjunto. Isso inclui bancos de dados, serviços de cache, serviços de mensageria etc. &lt;/p&gt;

&lt;p&gt;Na teoria, tudo parece interessante e simples. Mas esses testes podem gerar e alterar um grande volume de dados, então é necessário tomar cuidado com os recursos utilizados. Até porque acidentes acontecem, e talvez, em um descuido, você pode acabar executando um DELETE sem WHERE, levando à exclusão total de uma tabela. 😅&lt;/p&gt;

&lt;p&gt;Para evitar esse tipo de problemas, é possível criar esses recursos a partir de containers Docker por meio da lib &lt;a href="https://testcontainers.com/" rel="noopener noreferrer"&gt;TestContainers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Neste tutorial, explicarei os passos para a utilização desses containers em uma API .Net.&lt;/p&gt;

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

&lt;p&gt;O projeto completo pode ser encontrado &lt;a href="https://github.com/madebyluque/TutorialIntegrationTests" rel="noopener noreferrer"&gt;neste link&lt;/a&gt;. Trata-se de uma API de gerenciamento de tarefas (a famosa "To-Do list"). Ela consiste de basicamente 3 partes: &lt;/p&gt;

&lt;p&gt;Uma entidade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace IntegrationTestingDemo.API;

public class Todo
{
    public Todo()
    {
    }

    public Todo(string title, string description)
    {
        Title = title;
        Description = description;
        Id = Guid.NewGuid().ToString().Replace("-", "");
        CreatedAt = DateTime.UtcNow;
        Done = false;
    }

    public string Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public bool Done { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? CompletedAt { get; set; }
}

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

&lt;/div&gt;



&lt;p&gt;Um service com a lógica da aplicação (visando a simplicidade, algumas operações não foram criadas):&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 TodoService(TodoContext context) : ITodoService
{
    private readonly TodoContext _context = context;

    public async Task&amp;lt;string&amp;gt; Create(string title, string description)
    {
        var todo = new Todo(title, description);
        await _context.AddAsync(todo);
        await _context.SaveChangesAsync();
        return todo.Id;
    }

    public async Task&amp;lt;List&amp;lt;Todo&amp;gt;&amp;gt; GetAll()
    {
        return await _context.Todos.OrderBy(x =&amp;gt; x.CreatedAt).ToListAsync();
    }

    public async Task&amp;lt;Todo&amp;gt; GetById(string id)
    {
        return await _context.Todos.FirstOrDefaultAsync(x =&amp;gt; x.Id == id);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E um controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ApiController]
[Route("[controller]")]
public class TodoController(ITodoService todoService) : ControllerBase
{
    private readonly ITodoService _todoService = todoService;

    [HttpPost]
    public async Task&amp;lt;IActionResult&amp;gt; Create([FromBody] CreateTodoModel model)
    {
        var result = await _todoService.Create(model.Title, model.Description);
        return CreatedAtRoute(nameof(GetById), routeValues: new { Id = result }, result);
    }

    [HttpGet]
    public async Task&amp;lt;IActionResult&amp;gt; GetAll()
    {
        var todos = await _todoService.GetAll();
        return Ok(todos);
    }

    [HttpGet("{id}", Name = "GetById")]
    public async Task&amp;lt;IActionResult&amp;gt; GetById(string id)
    {
        var todo = await _todoService.GetById(id);
        return Ok(todo);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testes
&lt;/h2&gt;

&lt;p&gt;A configuração de &lt;strong&gt;TestContainer&lt;/strong&gt; é feita na criação da &lt;strong&gt;WebApplicationFactory&lt;/strong&gt; para os testes de integração. Neste tutorial, decidi utilizar PostgreSQL. A criação de um container desse banco de dados pode ser feita da seguinte forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder().WithUsername("postgres").WithPassword("postgres").Build();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É possível alterar o usuário e a senha da forma que desejar. Há, inclusive, a opção de alterar outras configurações no builder, como o nome do db, o host etc.&lt;/p&gt;

&lt;p&gt;Com o container criado, é possível obter a connection string dele da seguinte forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_postgres.GetConnectionString()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pode ser necessário remover o dbContext da aplicação para adicionar um novo com a connection string do container de teste. Nesse caso, é possível fazê-lo da seguinte forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var context = services.FirstOrDefault(descriptor =&amp;gt; descriptor.ServiceType == typeof(TodoContext));
if (context != null)
{
    services.Remove(context);
    var options = services.Where(r =&amp;gt; (r.ServiceType == typeof(DbContextOptions))
      || (r.ServiceType.IsGenericType &amp;amp;&amp;amp; r.ServiceType.GetGenericTypeDefinition() == typeof(DbContextOptions&amp;lt;&amp;gt;))).ToArray();
    foreach (var option in options)
    {
        services.Remove(option);
    }
}

services.AddDbContext&amp;lt;TodoContext&amp;gt;(options =&amp;gt;
{
    options.UseNpgsql(_postgres.GetConnectionString());
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por fim, é interessante que sua classe de &lt;strong&gt;WebApplicationFactory&lt;/strong&gt; implemente a interface IAsyncLifetime para que o container criado seja inicializado / parado.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public Task InitializeAsync()
{
    return _postgres.StartAsync();
}

public new Task DisposeAsync()
{
    return _postgres.StopAsync();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com a configuração feita, já é possível criar testes de integração. No teste abaixo, utilizei o TodoService para criar uma tarefa, e então verifiquei se os dados no banco de dados estavam de acordo com o que deveriam:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Fact]
public async Task Create_ShouldCreateTodoAndReturnItsId()
{
    // Act
    var result = await _todoService.Create(TestTitle, TestDescription);

    // Assert
    var todo = await _dbContext.Todos.FirstOrDefaultAsync(x =&amp;gt; x.Id == result);
    Assert.NotNull(todo);
    Assert.False(todo.Done);
    Assert.Equal(TestTitle, todo.Title);
    Assert.Equal(TestDescription, todo.Description);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Acesse o &lt;a href="https://github.com/madebyluque/TutorialIntegrationTests" rel="noopener noreferrer"&gt;repositório&lt;/a&gt; para verificar os demais testes.)&lt;/p&gt;

&lt;p&gt;O exemplo acima testa uma classe simples. Entretanto, poderia testar uma classe mais complexa, como um Handler, que manipula os dados através de diversos objetos. Além disso, o service foi criado para não testar as chamadas diretas ao controller.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub actions
&lt;/h2&gt;

&lt;p&gt;É possível integrar os testes à pipeline. &lt;a href="https://www.milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet" rel="noopener noreferrer"&gt;Essa postagem do Milan Jovanović&lt;/a&gt; mostra como integrá-los a uma Github Action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Run Tests 🚀

on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  run-tests:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '8.0.x'

      - name: Restore
        run: dotnet restore ./IntegrationTestingDemo.sln

      - name: Build
        run: dotnet build ./IntegrationTestingDemo.sln --no-restore

      - name: Test
        run: dotnet test ./IntegrationTestingDemo.sln --no-build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>dotnet</category>
      <category>testing</category>
      <category>docker</category>
    </item>
    <item>
      <title>TestContainers for Integration Testing with .NET</title>
      <dc:creator>👨‍💻 Lucas Silva</dc:creator>
      <pubDate>Tue, 06 Feb 2024 20:11:23 +0000</pubDate>
      <link>https://forem.com/lukesilva/testcontainers-for-integration-testing-with-net-10nk</link>
      <guid>https://forem.com/lukesilva/testcontainers-for-integration-testing-with-net-10nk</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Unlike unit tests, integration tests allow validating the behavior of an application when all its components are used together. This includes databases, cache services, messaging services, etc.&lt;/p&gt;

&lt;p&gt;In theory, everything seems interesting and simple. But these tests can generate and alter a large volume of data, so it is necessary to be careful with the resources used. After all, accidents happen, and  you might end up executing a DELETE without WHERE, leading to the total deletion of a table. 😅&lt;/p&gt;

&lt;p&gt;To avoid these kinds of problems, it's possible to create these resources using Docker containers through the &lt;a href="https://testcontainers.com/" rel="noopener noreferrer"&gt;TestContainers library&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this tutorial, I'll explain the steps for using these containers in a .NET API.&lt;/p&gt;

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

&lt;p&gt;The complete project can be found at &lt;a href="https://github.com/madebyluque/TutorialIntegrationTests" rel="noopener noreferrer"&gt;this link&lt;/a&gt;. It's a task management API (the famous "To-Do list"). It consists basically of three parts:&lt;/p&gt;

&lt;p&gt;An entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace IntegrationTestingDemo.API;

public class Todo
{
    public Todo()
    {
    }

    public Todo(string title, string description)
    {
        Title = title;
        Description = description;
        Id = Guid.NewGuid().ToString().Replace("-", "");
        CreatedAt = DateTime.UtcNow;
        Done = false;
    }

    public string Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public bool Done { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? CompletedAt { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A service (for simplicity's sake, some operations were not created):&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 TodoService(TodoContext context) : ITodoService
{
    private readonly TodoContext _context = context;

    public async Task&amp;lt;string&amp;gt; Create(string title, string description)
    {
        var todo = new Todo(title, description);
        await _context.AddAsync(todo);
        await _context.SaveChangesAsync();
        return todo.Id;
    }

    public async Task&amp;lt;List&amp;lt;Todo&amp;gt;&amp;gt; GetAll()
    {
        return await _context.Todos.OrderBy(x =&amp;gt; x.CreatedAt).ToListAsync();
    }

    public async Task&amp;lt;Todo&amp;gt; GetById(string id)
    {
        return await _context.Todos.FirstOrDefaultAsync(x =&amp;gt; x.Id == id);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ApiController]
[Route("[controller]")]
public class TodoController(ITodoService todoService) : ControllerBase
{
    private readonly ITodoService _todoService = todoService;

    [HttpPost]
    public async Task&amp;lt;IActionResult&amp;gt; Create([FromBody] CreateTodoModel model)
    {
        var result = await _todoService.Create(model.Title, model.Description);
        return CreatedAtRoute(nameof(GetById), routeValues: new { Id = result }, result);
    }

    [HttpGet]
    public async Task&amp;lt;IActionResult&amp;gt; GetAll()
    {
        var todos = await _todoService.GetAll();
        return Ok(todos);
    }

    [HttpGet("{id}", Name = "GetById")]
    public async Task&amp;lt;IActionResult&amp;gt; GetById(string id)
    {
        var todo = await _todoService.GetById(id);
        return Ok(todo);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The TestContainer configuration is done when creating the WebApplicationFactory for integration tests. For this tutorial, I decided to use PostgreSQL. Creating it can be done as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder().WithUsername("postgres").WithPassword("postgres").Build();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can change the username and password as desired. There is also the option to change other settings in the builder, such as the db name, host etc.&lt;/p&gt;

&lt;p&gt;You can obtain the container's connection string as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_postgres.GetConnectionString()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may be necessary to remove the dbContext from the application to add a new one with the connection string from the test container. If that's the case, you can do it as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var context = services.FirstOrDefault(descriptor =&amp;gt; descriptor.ServiceType == typeof(TodoContext));
if (context != null)
{
    services.Remove(context);
    var options = services.Where(r =&amp;gt; (r.ServiceType == typeof(DbContextOptions))
      || (r.ServiceType.IsGenericType &amp;amp;&amp;amp; r.ServiceType.GetGenericTypeDefinition() == typeof(DbContextOptions&amp;lt;&amp;gt;))).ToArray();
    foreach (var option in options)
    {
        services.Remove(option);
    }
}

services.AddDbContext&amp;lt;TodoContext&amp;gt;(options =&amp;gt;
{
    options.UseNpgsql(_postgres.GetConnectionString());
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, it is interesting that your WebApplicationFactory class implements the IAsyncLifetime interface so that the created container is initialized/stopped.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public Task InitializeAsync()
{
    return _postgres.StartAsync();
}

public new Task DisposeAsync()
{
    return _postgres.StopAsync();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the setup done, it is already possible to create integration tests. In the test below, I've used TodoService to create a Todo, then checked if its data was saved as expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Fact]
public async Task Create_ShouldCreateTodoAndReturnItsId()
{
    // Act
    var result = await _todoService.Create(TestTitle, TestDescription);

    // Assert
    var todo = await _dbContext.Todos.FirstOrDefaultAsync(x =&amp;gt; x.Id == result);
    Assert.NotNull(todo);
    Assert.False(todo.Done);
    Assert.Equal(TestTitle, todo.Title);
    Assert.Equal(TestDescription, todo.Description);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GitHub actions
&lt;/h2&gt;

&lt;p&gt;It is possible to integrate the tests into a pipeline. &lt;a href="https://www.milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet" rel="noopener noreferrer"&gt;This post by Milan Jovanović&lt;/a&gt; shows how to integrate them into a Github Action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Run Tests 🚀

on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  run-tests:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '8.0.x'

      - name: Restore
        run: dotnet restore ./IntegrationTestingDemo.sln

      - name: Build
        run: dotnet build ./IntegrationTestingDemo.sln --no-restore

      - name: Test
        run: dotnet test ./IntegrationTestingDemo.sln --no-build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the end of the tutorial. I hope it has been enough to help you implement integration tests with TestContainers in your application.&lt;/p&gt;

&lt;p&gt;Until next time!&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>testing</category>
      <category>docker</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>TestContainers para testes de integração com .Net</title>
      <dc:creator>👨‍💻 Lucas Silva</dc:creator>
      <pubDate>Tue, 06 Feb 2024 19:58:04 +0000</pubDate>
      <link>https://forem.com/lukesilva/testcontainers-para-testes-de-integracao-com-net-1c1d</link>
      <guid>https://forem.com/lukesilva/testcontainers-para-testes-de-integracao-com-net-1c1d</guid>
      <description>&lt;h2&gt;
  
  
  Introdução
&lt;/h2&gt;

&lt;p&gt;Diferente de testes de unidade, os testes de integração permitem validar o comportamento de uma aplicação quando todos os componentes dela são utilizados em conjunto. Isso inclui bancos de dados, serviços de cache, serviços de mensageria etc. &lt;/p&gt;

&lt;p&gt;Na teoria, tudo parece interessante e simples. Mas esses testes podem gerar e alterar um grande volume de dados, então é necessário tomar cuidado com os recursos utilizados. Até porque acidentes acontecem, e talvez, em um descuido, você pode acabar executando um DELETE sem WHERE, levando à exclusão total de uma tabela. 😅&lt;/p&gt;

&lt;p&gt;Para evitar esse tipo de problemas, é possível criar esses recursos a partir de containers Docker por meio da lib &lt;a href="https://testcontainers.com/" rel="noopener noreferrer"&gt;TestContainers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Neste tutorial, explicarei os passos para a utilização desses containers em uma API .Net.&lt;/p&gt;

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

&lt;p&gt;O projeto completo pode ser encontrado &lt;a href="https://github.com/madebyluque/TutorialIntegrationTests" rel="noopener noreferrer"&gt;neste link&lt;/a&gt;. Trata-se de uma API de gerenciamento de tarefas (a famosa "To-Do list"). Ela consiste de basicamente 3 partes: &lt;/p&gt;

&lt;p&gt;Uma entidade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;namespace IntegrationTestingDemo.API;

public class Todo
{
    public Todo()
    {
    }

    public Todo(string title, string description)
    {
        Title = title;
        Description = description;
        Id = Guid.NewGuid().ToString().Replace("-", "");
        CreatedAt = DateTime.UtcNow;
        Done = false;
    }

    public string Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public bool Done { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? CompletedAt { get; set; }
}

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

&lt;/div&gt;



&lt;p&gt;Um service com a lógica da aplicação (visando a simplicidade, algumas operações não foram criadas):&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 TodoService(TodoContext context) : ITodoService
{
    private readonly TodoContext _context = context;

    public async Task&amp;lt;string&amp;gt; Create(string title, string description)
    {
        var todo = new Todo(title, description);
        await _context.AddAsync(todo);
        await _context.SaveChangesAsync();
        return todo.Id;
    }

    public async Task&amp;lt;List&amp;lt;Todo&amp;gt;&amp;gt; GetAll()
    {
        return await _context.Todos.OrderBy(x =&amp;gt; x.CreatedAt).ToListAsync();
    }

    public async Task&amp;lt;Todo&amp;gt; GetById(string id)
    {
        return await _context.Todos.FirstOrDefaultAsync(x =&amp;gt; x.Id == id);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E um controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ApiController]
[Route("[controller]")]
public class TodoController(ITodoService todoService) : ControllerBase
{
    private readonly ITodoService _todoService = todoService;

    [HttpPost]
    public async Task&amp;lt;IActionResult&amp;gt; Create([FromBody] CreateTodoModel model)
    {
        var result = await _todoService.Create(model.Title, model.Description);
        return CreatedAtRoute(nameof(GetById), routeValues: new { Id = result }, result);
    }

    [HttpGet]
    public async Task&amp;lt;IActionResult&amp;gt; GetAll()
    {
        var todos = await _todoService.GetAll();
        return Ok(todos);
    }

    [HttpGet("{id}", Name = "GetById")]
    public async Task&amp;lt;IActionResult&amp;gt; GetById(string id)
    {
        var todo = await _todoService.GetById(id);
        return Ok(todo);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testes
&lt;/h2&gt;

&lt;p&gt;A configuração de &lt;strong&gt;TestContainer&lt;/strong&gt; é feita na criação da &lt;strong&gt;WebApplicationFactory&lt;/strong&gt; para os testes de integração. Neste tutorial, decidi utilizar PostgreSQL. A criação de um container desse banco de dados pode ser feita da seguinte forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder().WithUsername("postgres").WithPassword("postgres").Build();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;É possível alterar o usuário e a senha da forma que desejar. Há, inclusive, a opção de alterar outras configurações no builder, como o nome do db, o host etc.&lt;/p&gt;

&lt;p&gt;Com o container criado, é possível obter a connection string dele da seguinte forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_postgres.GetConnectionString()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pode ser necessário remover o dbContext da aplicação para adicionar um novo com a connection string do container de teste. Nesse caso, é possível fazê-lo da seguinte forma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var context = services.FirstOrDefault(descriptor =&amp;gt; descriptor.ServiceType == typeof(TodoContext));
if (context != null)
{
    services.Remove(context);
    var options = services.Where(r =&amp;gt; (r.ServiceType == typeof(DbContextOptions))
      || (r.ServiceType.IsGenericType &amp;amp;&amp;amp; r.ServiceType.GetGenericTypeDefinition() == typeof(DbContextOptions&amp;lt;&amp;gt;))).ToArray();
    foreach (var option in options)
    {
        services.Remove(option);
    }
}

services.AddDbContext&amp;lt;TodoContext&amp;gt;(options =&amp;gt;
{
    options.UseNpgsql(_postgres.GetConnectionString());
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por fim, é interessante que sua classe de &lt;strong&gt;WebApplicationFactory&lt;/strong&gt; implemente a interface IAsyncLifetime para que o container criado seja inicializado / parado.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public Task InitializeAsync()
{
    return _postgres.StartAsync();
}

public new Task DisposeAsync()
{
    return _postgres.StopAsync();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com a configuração feita, já é possível criar testes de integração. No teste abaixo, utilizei o TodoService para criar uma tarefa, e então verifiquei se os dados no banco de dados estavam de acordo com o que deveriam:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Fact]
public async Task Create_ShouldCreateTodoAndReturnItsId()
{
    // Act
    var result = await _todoService.Create(TestTitle, TestDescription);

    // Assert
    var todo = await _dbContext.Todos.FirstOrDefaultAsync(x =&amp;gt; x.Id == result);
    Assert.NotNull(todo);
    Assert.False(todo.Done);
    Assert.Equal(TestTitle, todo.Title);
    Assert.Equal(TestDescription, todo.Description);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Acesse o &lt;a href="https://github.com/madebyluque/TutorialIntegrationTests" rel="noopener noreferrer"&gt;repositório&lt;/a&gt; para verificar os demais testes.)&lt;/p&gt;

&lt;p&gt;O exemplo acima testa uma classe simples. Entretanto, poderia testar uma classe mais complexa, como um Handler, que manipula os dados através de diversos objetos. Além disso, o service foi criado para não testar as chamadas diretas ao controller.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub actions
&lt;/h2&gt;

&lt;p&gt;É possível integrar os testes à pipeline. &lt;a href="https://www.milanjovanovic.tech/blog/testcontainers-integration-testing-using-docker-in-dotnet" rel="noopener noreferrer"&gt;Essa postagem do Milan Jovanović&lt;/a&gt; mostra como integrá-los a uma Github Action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Run Tests 🚀

on:
  workflow_dispatch:
  push:
    branches:
      - main

jobs:
  run-tests:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3

      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: '8.0.x'

      - name: Restore
        run: dotnet restore ./IntegrationTestingDemo.sln

      - name: Build
        run: dotnet build ./IntegrationTestingDemo.sln --no-restore

      - name: Test
        run: dotnet test ./IntegrationTestingDemo.sln --no-build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Este é o fim do tutorial. Espero que esse texto tenha sido o suficiente para te ajudar a implementar testes de integração com TestContainers na sua aplicação. &lt;/p&gt;

&lt;p&gt;Até a próxima!&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>testing</category>
      <category>docker</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>An easy and simple way to implement soft delete in .Net using EF Core</title>
      <dc:creator>👨‍💻 Lucas Silva</dc:creator>
      <pubDate>Fri, 19 Jan 2024 14:50:25 +0000</pubDate>
      <link>https://forem.com/lukesilva/an-easy-and-simple-way-to-implement-soft-delete-in-net-using-ef-core-4g3k</link>
      <guid>https://forem.com/lukesilva/an-easy-and-simple-way-to-implement-soft-delete-in-net-using-ef-core-4g3k</guid>
      <description>&lt;p&gt;When developing APIs, it's common to have to modify the behavior of DELETE so that instead of deleting a record, it simply changes a boolean property and considers that resource not to exist if the value of the property is false. This technique is known as "Soft Delete."&lt;/p&gt;

&lt;p&gt;There are many ways to implement this behavior. The approach presented in this text can be summarized in the following steps:&lt;br&gt;
1- Add a boolean property to the desired class.&lt;br&gt;
2- Override the SaveChangesAsync method of the class that extends DbContext.&lt;br&gt;
3- Add a filter to the entity configuration so that "inactive" entities are not returned in queries.&lt;/p&gt;
&lt;h2&gt;
  
  
  Add boolean property
&lt;/h2&gt;

&lt;p&gt;The first step is self-explanatory. Create an entity and add the boolean property to it. If multiple entities need this behavior, consider creating a base class or interface that contains it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Override the SaveChangesAsync method
&lt;/h2&gt;

&lt;p&gt;If you use EF Core, you likely extended the DbContext class to create your context. The base class has a method called SaveChangesAsync, which saves the changes made to the database.&lt;/p&gt;

&lt;p&gt;The following code aims to detect the state of the entities tracked by the ChangeTracker. If any has been deleted, it will change its state to Unchanged (instead of Deleted) and then change the boolean property added in the previous step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public override Task&amp;lt;int&amp;gt; SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
    foreach (var entry in ChangeTracker.Entries&amp;lt;T&amp;gt;()) // Where "T" is the entity type
    {
        switch (entry.State)
        {
            case EntityState.Deleted:
                entry.State = EntityState.Unchanged;
                entry.Entity.BooleanProperty = false; // Prefer using a method for this.
                break;
        }
    }
    return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add filter
&lt;/h2&gt;

&lt;p&gt;With the modification made, it's now possible to update all queries so they ignore entities where the value of the boolean property is false. However, depending on the size of your application, this may be a quite tedious task.&lt;/p&gt;

&lt;p&gt;To avoid the described situation, EF provides a way to do this automatically through entity configuration. Example:&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 YourEntityConfiguration : IEntityTypeConfiguration&amp;lt;YourEntity&amp;gt;
{
    public void Configure(EntityTypeBuilder&amp;lt;YourEntity&amp;gt; builder)
    {
        builder.HasQueryFilter(x =&amp;gt; x.Active);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code ensures that only entities with the Active property set to true are returned in queries.&lt;/p&gt;

&lt;p&gt;We've reached the end of the tutorial. I hope this text has been enough to implement basic Soft Delete behavior in your application.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>api</category>
    </item>
    <item>
      <title>Uma forma simples de realizar soft delete em .Net utilizando Entity Framework Core</title>
      <dc:creator>👨‍💻 Lucas Silva</dc:creator>
      <pubDate>Fri, 19 Jan 2024 14:26:31 +0000</pubDate>
      <link>https://forem.com/lukesilva/uma-forma-simples-de-realizar-soft-delete-em-net-utilizando-entity-framework-core-1j7d</link>
      <guid>https://forem.com/lukesilva/uma-forma-simples-de-realizar-soft-delete-em-net-utilizando-entity-framework-core-1j7d</guid>
      <description>&lt;p&gt;Ao desenvolver APIs, é comum ter de alterar o comportamento de DELETE para que, em vez de excluir o registro, simplesmente alterar uma propriedade booleana e considerar que aquele recurso não existe caso o valor da propriedade seja &lt;em&gt;false&lt;/em&gt;. Essa técnica é conhecida como &lt;strong&gt;"Soft Delete"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Há várias formas de implementar esse comportamento. A forma apresentada neste texto pode ser resumida aos seguintes passos:&lt;br&gt;
1- Adicionar uma propriedade booleana à classe desejada;&lt;br&gt;
2- Sobrescrever o método &lt;strong&gt;SaveChangesAsync&lt;/strong&gt; da classe que estende &lt;strong&gt;DbContext&lt;/strong&gt;;&lt;br&gt;
3- Adicionar um filtro na configuração da entidade para que as entidades "inativas" não sejam retornadas em queries.&lt;/p&gt;
&lt;h2&gt;
  
  
  Adicionar propriedade booleana
&lt;/h2&gt;

&lt;p&gt;O primeiro passo é autoexplicativo. Crie uma entidade e adicione a propriedade booleana a ela. Caso várias entidades precisem desse comportamento, considere criar uma classe base ou interface que a contenha.&lt;/p&gt;
&lt;h2&gt;
  
  
  Sobrescrever o método SaveChangesAsync
&lt;/h2&gt;

&lt;p&gt;Se você utiliza o EF, é provável que tenha estendido a classe &lt;strong&gt;DbContext&lt;/strong&gt; para criar um contexto próprio. A classe base possui um método chamado &lt;strong&gt;SaveChangesAsync&lt;/strong&gt;, o qual salva as alterações feitas no banco de dados.&lt;br&gt;&lt;br&gt;
A alteração que será realizada no método visa detectar o estado das  entidades rastreadas pelo &lt;a href="https://learn.microsoft.com/en-us/ef/core/change-tracking/" rel="noopener noreferrer"&gt;ChangeTracker&lt;/a&gt;. Caso alguma tenha sido deletada, será necessário mudar o estado dela para &lt;strong&gt;Unchanged&lt;/strong&gt; (em vez de &lt;strong&gt;Deleted&lt;/strong&gt;), e então alterar a propriedade booleana adicionada no passo anterior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public override Task&amp;lt;int&amp;gt; SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
{
    foreach (var entry in ChangeTracker.Entries&amp;lt;T&amp;gt;()) // Onde "T" é o tipo da entidade
    {
        switch (entry.State)
        {
            case EntityState.Deleted:
                entry.State = EntityState.Unchanged;
                entry.Entity.PropriedadeBooleana = false; // Prefira utilizar um método para isso.
                break;
        }
    }
    return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adicionar filtro
&lt;/h2&gt;

&lt;p&gt;Com a alteração feita, já é possível alterar todas as queries para que elas deconsiderem entidades às quais o valor da propriedade booleana seja &lt;strong&gt;false&lt;/strong&gt;. Entretanto, dependendo do tamanho da sua aplicação, essa tarefa poderá ser bastante trabalhosa.&lt;/p&gt;

&lt;p&gt;Para evitar a situação descrita acima, o EF fornece uma forma de realizar isso de forma automática através da configuração da entidade. Exemplo:&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 SuaEntidadeConfiguration: IEntityTypeConfiguration&amp;lt;SuaEntidade&amp;gt;
{
    public void Configure(EntityTypeBuilder&amp;lt;SuaEntidade&amp;gt; builder)
    {
        builder.HasQueryFilter(x =&amp;gt; x.Active);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O código acima faz com que somente as entidades que possuam a propriedade &lt;strong&gt;Active&lt;/strong&gt; como &lt;strong&gt;true&lt;/strong&gt; sejam retornadas em caso de queries. &lt;/p&gt;

&lt;p&gt;Chegamos ao fim do tutorial. Espero que este texto tenha sido o suficiente para a implementação de um comportamento básico de &lt;strong&gt;Soft delete&lt;/strong&gt; na sua aplicação. &lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>api</category>
    </item>
    <item>
      <title>Github action para jogos da Unity</title>
      <dc:creator>👨‍💻 Lucas Silva</dc:creator>
      <pubDate>Sun, 16 Jul 2023 19:31:32 +0000</pubDate>
      <link>https://forem.com/lukesilva/github-action-para-jogos-da-unity-17hd</link>
      <guid>https://forem.com/lukesilva/github-action-para-jogos-da-unity-17hd</guid>
      <description>&lt;p&gt;Após brincar com desenvolvimento de jogos por alguns anos, decidi levar a sério a carreira de game dev e abri um estúdio de jogos chamado Blue Flower Game Lab.&lt;br&gt;
Estou desenvolvendo meu primeiro projeto sozinho, então tenho tentado automatizar meus processos ao máximo. Foi assim que descobri a &lt;a href="https://game.ci/" rel="noopener noreferrer"&gt;Game.ci&lt;/a&gt;, uma maneira de automatizar builds de projetos da Unity através do GitHub Actions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Configuração
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Ativação e secrets
&lt;/h3&gt;

&lt;p&gt;Crie um arquivo chamado &lt;strong&gt;.github/workflows/activation.yml&lt;/strong&gt; e cole o seguinte conteúdo nele:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Acquire activation file
on:
  workflow_dispatch: {}
jobs:
  activation:
    name: Request manual activation file 🔑
    runs-on: ubuntu-latest
    steps:
      # Request manual activation file
      - name: Request manual activation file
        id: getManualLicenseFile
        uses: game-ci/unity-request-activation-file@v2
      # Upload artifact (Unity_v20XX.X.XXXX.alf)
      - name: Expose as artifact
        uses: actions/upload-artifact@v2
        with:
          name: ${{ steps.getManualLicenseFile.outputs.filePath }}
          path: ${{ steps.getManualLicenseFile.outputs.filePath }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Execute essa ação. O resultado dela será um arquivo de ativação manual. Depois disso, pegue o arquivo e faça o upload dele em &lt;a href="https://license.unity3d.com/manual" rel="noopener noreferrer"&gt;license.unity3d.com&lt;/a&gt; para obter um arquivo de licença.&lt;br&gt;
Com o arquivo de licença em mãos, será necessário criar os seguintes segredos no repositório do seu jogo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UNITY_LICENSE: conteúdo obtido no arquivo de licença;&lt;/li&gt;
&lt;li&gt;UNITY_EMAIL: e-mail de login na Unity;&lt;/li&gt;
&lt;li&gt;UNITY_PASSWORD: senha para login na Unity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; a senha e o e-mail não são solicitados nem armazenados pela game.ci. São necessárias apenas para reativação da licença durante o processo de build.&lt;/p&gt;
&lt;h3&gt;
  
  
  Build
&lt;/h3&gt;

&lt;p&gt;Após obter a licença e configurar os secrets, crie um novo arquivo de pipeline com o seguinte conteúdo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Build

on:
  push:
    branches:
      - main

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        targetPlatform:
          # - StandaloneOSX # Build a macOS standalone (Intel 64-bit).
          # - StandaloneWindows # Build a Windows standalone.
          # - StandaloneWindows64 # Build a Windows 64-bit standalone.
          # - StandaloneLinux64 # Build a Linux 64-bit standalone.
          # - iOS # Build an iOS player.
          # - Android # Build an Android .apk standalone app.
          # - WebGL # WebGL.
    steps:
      # Checkout
      - name: Checkout repository
        uses: actions/checkout@v2
        with:
          lfs: true

      # Cache
      - uses: actions/cache@v2
        with:
          path: Library
          key: Library-${{ hashFiles('Assets/**', 'Packages/**', 'ProjectSettings/**') }}
          restore-keys: |
            Library-

      # Build
      - name: Build project
        uses: game-ci/unity-builder@v2
        env:
          UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
          UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
          UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
        with:
          targetPlatform: ${{ matrix.targetPlatform }}

      # Output
      - uses: actions/upload-artifact@v2
        with:
          name: Build
          path: build

      # Discord notification
      - name: Discord Notifier
        uses: madebyluque/action-discord-notifier@v1.1.1
        with:
          webhook: Webhook do canal do discord
          message-title: Título da mensagem do discord
          message-description: Descrição da mensagem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com o arquivo criado, você precisará apenas selecionar as plataformas alvo. Para isso, remova o símbolo "#" das plataformas desejadas na seção &lt;strong&gt;targetPlatform&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;O último passo da pipeline envia uma notificação a um canal do Discord. Como a execução dela pode passar de 10 minutos, optei por ser avisado da conclusão dela em vez de ficar esperando. Para isso, fiz o fork de uma ação no github e modifiquei algumas informações para poder personalizar o título e a descrição da mensagem. Esse passo é opcional, então fique à vontade para removê-lo. &lt;/p&gt;

</description>
      <category>unity3d</category>
      <category>gamedev</category>
      <category>github</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Recursos e dicas para o desenvolvimento do seu primeiro game</title>
      <dc:creator>👨‍💻 Lucas Silva</dc:creator>
      <pubDate>Mon, 20 Feb 2023 19:04:14 +0000</pubDate>
      <link>https://forem.com/lukesilva/recursos-e-dicas-para-o-desenvolvimento-do-seu-primeiro-game-5gne</link>
      <guid>https://forem.com/lukesilva/recursos-e-dicas-para-o-desenvolvimento-do-seu-primeiro-game-5gne</guid>
      <description>&lt;p&gt;Fotos de Foto de &lt;a href="https://unsplash.com/es/@sigmund?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Sigmund&lt;/a&gt; e &lt;a href="https://unsplash.com/@dylu?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Jacek Dylag&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Bem, é muito provável que você, assim como eu, sempre sonhou em desenvolver seus próprios jogos. Assim que mudei de carreira e comecei a programar, decidi que tiraria esse sonho do papel e o tornaria realidade. Ainda não consegui terminar meu game, mas as coisas estão caminhando bem. Trarei aqui algumas dicas para que você possa fazer o mesmo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Antes de mais nada: é um processo longo e trabalhoso
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/JWybLzXs7Hn0JKhSji/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/JWybLzXs7Hn0JKhSji/giphy.gif" title="Muito trabalhoso!" alt="Muito trabalhoso!" width="480" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Se você está começando do zero, saiba que criar um jogo é um processo trabalhoso. Sério. Você pode agilizá-lo com algumas das dicas deste texto, mas no geral, você vai passar alguns meses/anos trabalhando na arte, no código, no design de níveis, no som...&lt;br&gt;
Inclusive, saiba que todo projeto de vida tem um custo muito alto: seu tempo. Entretanto, mesmo que você não se torne o novo &lt;a href="https://pt.wikipedia.org/wiki/Eric_Barone" rel="noopener noreferrer"&gt;Eric Barone&lt;/a&gt;, o conhecimento adquirido ao &lt;strong&gt;criar um projeto seu&lt;/strong&gt;, é extremamente valioso.&lt;/p&gt;

&lt;h2&gt;
  
  
  Game Engine
&lt;/h2&gt;

&lt;p&gt;Se você estiver pensando em realizar o seu primeiro projeto, não comece por criar uma game engine. Há milhares de engines gratuitas por aí, então &lt;strong&gt;não reinvente a roda&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://i.giphy.com/media/3o6gE9BYreSsQyOD9C/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/3o6gE9BYreSsQyOD9C/giphy.gif" title="Não reinvente a roda" alt="Não reinvente a roda" width="480" height="480"&gt;&lt;/a&gt;&lt;br&gt;
Uma game engine é um software voltado ao desenvolvimento de jogos. Será nela que você criará seus níveis, scripts, mecânicas do jogo etc. A escolha de uma boa game engine basicamente ditará se o seu projeto será terminado ou não.&lt;br&gt;
Há alguns pontos a serem considerados, como facilidade para gerar as versões finais (builds) do projeto, facilidade para criação de scripts, facilidade para trabalhar os objetos etc.&lt;br&gt;
Pensando nisso, a minha escolha pessoal é a &lt;a href="https://unity.com/pt" rel="noopener noreferrer"&gt;Unity&lt;/a&gt;. É possível criar jogos 2D, 3D, jogos para realidade virtual e outros. Além disso, ela simplifica o processo de "buildar" seu jogo para celulares, pcs, consoles etc.&lt;br&gt;
A linguagem de programação da Unity é o C#, uma linguagem vastamente consolidada no mercado. A documentação dela é completa e há tutoriais incríveis sobre ela na internet. Entretanto, &lt;strong&gt;há um ponto de atenção&lt;/strong&gt;: por mais que a documentação da linguagem seja incrível, a documentação da unity não é muito boa, e muitas vezes você precisará recorrer a canais do Youtube (veja nas referências).&lt;br&gt;
Eu tentei outras das engines que recomendam na internet mas não consegui avançar muito com elas. Caso queira saber mais, os links são:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://gamemaker.io/pt-BR/download" rel="noopener noreferrer"&gt;GameMaker&lt;/a&gt;: você não precisa saber programar para conseguir fazer um jogo usando ela;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://godotengine.org/" rel="noopener noreferrer"&gt;Godot&lt;/a&gt;;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Arte visual
&lt;/h2&gt;

&lt;p&gt;Essa tem sido a parte mais difícil nos meus projetos. Eu até consigo desenhar, mas criar artes que encham as pessoas de sensações não é uma tarefa fácil. E não se engane: muitas pessoas abandonarão seu game se a arte não interessá-las. &lt;br&gt;
Isso não significa que seu game precisa ser extremamente realista. Ele só precisa ser agradável aos olhos, e isso é bastante subjetivo (logo, não tente agradar todo mundo).&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%2Fjhqos981uvtyvbw997i6.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%2Fjhqos981uvtyvbw997i6.png" title="Planet of Lana" alt="Planet of Lana" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frre2f17oof9013frf2bc.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%2Frre2f17oof9013frf2bc.png" title="Child of light" alt="Child of light" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevu1qasdw2z1raqgtzhx.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%2Fevu1qasdw2z1raqgtzhx.png" title="Will it Snail" alt="Will it Snail" width="650" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcsnqjps8jbeunnhun0bf.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%2Fcsnqjps8jbeunnhun0bf.png" title="Stardew Valley" alt="Stardew Valley" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Você pode usar uma ou várias ferramentas para criar sua arte. Aqui estão algumas das minhas favoritas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://krita.org/" rel="noopener noreferrer"&gt;Krita&lt;/a&gt;: desenho e pintura digital;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.piskelapp.com/" rel="noopener noreferrer"&gt;Piskel&lt;/a&gt;: pixel art;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.blender.org/" rel="noopener noreferrer"&gt;Blender&lt;/a&gt;: arte e modelagem 3D;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mapeditor.org/" rel="noopener noreferrer"&gt;Tiled&lt;/a&gt;: criação de tilemaps e editor de níveis;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.pureref.com/" rel="noopener noreferrer"&gt;PureRef&lt;/a&gt;: referência de imagens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Entretanto, assim como com as Game Engines, você não precisa criar tudo do zero. Há sites que vendem assets ou até mesmo disponibilizam eles gratuitamente. O canal &lt;a href="https://www.youtube.com/@DavidWehleGames" rel="noopener noreferrer"&gt;Game Dev Unlocked&lt;/a&gt; fala sobre como assets gratuitos foram importantes na criação do game &lt;a href="https://store.steampowered.com/app/555150/The_First_Tree/" rel="noopener noreferrer"&gt;The First Tree&lt;/a&gt;. Você pode encontrar esses assets no &lt;a href="https://itch.io/game-assets" rel="noopener noreferrer"&gt;itch&lt;/a&gt;, na &lt;a href="https://assetstore.unity.com/" rel="noopener noreferrer"&gt;Unity Asset Store&lt;/a&gt; ou em outros sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Som
&lt;/h2&gt;

&lt;p&gt;O som pode fazer seu jogo ser incrível ou extremamente chato &lt;br&gt;
 -- inclusive, não consigo jogar Splatoon devido às músicas apresentadas nos trailers. &lt;br&gt;
Gosto bastante do &lt;a href="https://lmms.io/download#windows" rel="noopener noreferrer"&gt;LMMS&lt;/a&gt; para criação de músicas. Entretanto, no meu projeto atual, decidi partir para algo mais manual, então comprei uma interface de áudio (&lt;a href="https://pt.aliexpress.com/item/1005003093878428.html" rel="noopener noreferrer"&gt;Teyun Q-22&lt;/a&gt;) e estou utilizando o &lt;a href="https://www.audacityteam.org/download/" rel="noopener noreferrer"&gt;Audacity&lt;/a&gt; e o Guitar Rig 5 para gravar músicas diretamente com a guitarra e o violão.&lt;br&gt;
Você precisará entender alguns conceitos antes de compor para seu game. Não basta apenas pegar um instrumento e sair tocando notas de forma desgovernada. A palestra do &lt;a href="https://www.youtube.com/watch?v=ELQoHDrv2hg" rel="noopener noreferrer"&gt;Leo Borges no GDTK 2021&lt;/a&gt; me ajudou a entender as necessidades sonoras do meu game, e recomendo que você a assista.&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%2F4st7kmeh6q5ndjwpmwb1.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%2F4st7kmeh6q5ndjwpmwb1.png" title="Jacek Dylag" alt="Jacek Dylag" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Há outros tipos de áudio em jogos. Sons de passos, de portas abrindo (quem lembra das portas de Resident Evil do PS1 se abrindo?). Costumo usar o site &lt;a href="https://opengameart.org/" rel="noopener noreferrer"&gt;OpenGameArt&lt;/a&gt; para buscar esse tipo de áudio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fim?
&lt;/h2&gt;

&lt;p&gt;Na verdade, &lt;strong&gt;espero que esse seja um início. Pelo menos para o seu projeto&lt;/strong&gt;. Senta na cadeira, escolhe a engine, baixa uns assets e começa o teu game. Sério. Não espere mais tempo para isso. &lt;br&gt;
Se, assim como eu, você tiver dúvidas se vale ou não a pena criar um jogo, minha resposta é: &lt;strong&gt;sempre vale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Se precisar de uma ajuda, uma mentoria, ou apenas quiser falar sobre game dev, siga-me no &lt;a href="https://twitter.com/cephalopodluke" rel="noopener noreferrer"&gt;twitter&lt;/a&gt; ou envie um e-mail para &lt;a href="mailto:lucas@blueflowergamelab.com"&gt;lucas@blueflowergamelab.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Enfim, espero que este texto tenha te ajudado. Vou deixar uns links na sessão de &lt;strong&gt;recursos&lt;/strong&gt; para você conseguir seguir adiante com seu projeto.&lt;/p&gt;

&lt;p&gt;Até a próxima!&lt;/p&gt;

&lt;h2&gt;
  
  
  Recursos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/@Blackthornprod" rel="noopener noreferrer"&gt;BlackThornProd - Youtube&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/@Brackeys" rel="noopener noreferrer"&gt;Brackeys - Youtube&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/@CodeMonkeyUnity" rel="noopener noreferrer"&gt;CodeMonkey - Youtube&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/@gamedevtoolkit" rel="noopener noreferrer"&gt;Gamedev Toolkit - Youtube&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opengameart.org/" rel="noopener noreferrer"&gt;OpenGameArt&lt;/a&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=jqWJsacaEi4" rel="noopener noreferrer"&gt;Seu jogo é MUITO grande! Como criar com limitações&lt;/a&gt;;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Automatizando a criação de requisições no JMeter utilizando Postman</title>
      <dc:creator>👨‍💻 Lucas Silva</dc:creator>
      <pubDate>Tue, 19 Jul 2022 01:51:29 +0000</pubDate>
      <link>https://forem.com/lukesilva/automatizando-a-criacao-de-requisicoes-no-jmeter-utilizando-postman-2cni</link>
      <guid>https://forem.com/lukesilva/automatizando-a-criacao-de-requisicoes-no-jmeter-utilizando-postman-2cni</guid>
      <description>&lt;p&gt;Faz um tempo que não escrevo por aqui, mas hoje, enquanto estudava para resolver um problema do trampo, me deparei com uma técnica que facilitou muito a forma de criar requisições no JMeter, então decidi compartilhá-la.&lt;/p&gt;

&lt;p&gt;Antes de mais nada, quero agradecer aos meus amigos &lt;a href="https://twitter.com/ojuninrocha" rel="noopener noreferrer"&gt;Jurno&lt;/a&gt; e &lt;a href="https://github.com/poferrari" rel="noopener noreferrer"&gt;João&lt;/a&gt;, que me guiaram na busca pela solução.&lt;/p&gt;

&lt;p&gt;Sem mais delongas, bora pro problema.&lt;/p&gt;

&lt;h2&gt;
  
  
  O problema
&lt;/h2&gt;

&lt;p&gt;Imagine que em um determinado momento, você e seu time precisarão realizar um teste de carga na aplicação que vocês desenvolvem. Entretanto, vocês possuem pouco ou nenhum conhecimento com a ferramenta JMeter, uma das mais utilizadas para esse tipo de tarefa. &lt;br&gt;
Uma das possíveis soluções é gastar um bom tempo lendo a documentação para aprender a criar as requisições uma a uma. Inclusive, essa teria sido a minha decisão, não fosse pelos conselhos dos amigos citados acima.&lt;/p&gt;

&lt;h2&gt;
  
  
  A solução
&lt;/h2&gt;

&lt;p&gt;É possível adicionar um elemento ao plano de teste do JMeter chamado &lt;a href="https://jmeter.apache.org/usermanual/jmeter_proxy_step_by_step.html" rel="noopener noreferrer"&gt;&lt;strong&gt;HTTP(S) Test Script Recorder&lt;/strong&gt;&lt;/a&gt;. Ele é responsável por gravar requisições http e transformá-las em uma HTTP Request (sampler). &lt;br&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%2F6gkliswkgkqjfr4upy0i.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%2F6gkliswkgkqjfr4upy0i.png" alt="Imagem da UI do JMeter mostrando o HTTPS Test Script Recorder" width="800" height="219"&gt;&lt;/a&gt;&lt;br&gt;
Com esse elemento adicionado, basta ajustar as configurações de proxy do Postman (ou qualquer outro sistema que você utiliza) para o endereço do Test Script Recorder (localhost:8888 por default), rodar o elemento e então realizar uma requisição no sistema a ser testado. &lt;br&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%2Frcnltqf07ag0br92nhlv.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%2Frcnltqf07ag0br92nhlv.png" alt="Configurações de proxy do postman" width="710" height="558"&gt;&lt;/a&gt;&lt;br&gt;
Fiz uma requisição de exemplo na &lt;a href="https://pokeapi.co/api/v2/pokemon/ditto" rel="noopener noreferrer"&gt;PokéAPI&lt;/a&gt;, e eis a requisição montada automaticamente no JMeter:&lt;br&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%2F29p2sjk2d7iewegegped.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%2F29p2sjk2d7iewegegped.png" alt="Interface do JMeter mostrando a requisição montada automaticamente" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;É possível que seja necessário realizar algumas alterações na requisição gerada, mas creio que o procedimento descrito acima seja um excelente ponto de entrada para requisições mais elaboradas.&lt;/p&gt;

&lt;p&gt;Bem, caso haja alguma dúvida sobre o texto ou alguma sugestão de melhoria, peço que envie um comentário 😎.&lt;br&gt;
Até a próxima!&lt;/p&gt;

</description>
      <category>testing</category>
      <category>postman</category>
      <category>jmeter</category>
    </item>
  </channel>
</rss>
