<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Paulo Neto</title>
    <description>The latest articles on DEV Community by Paulo Neto (@pauloneto).</description>
    <link>https://dev.to/pauloneto</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%2F1199297%2F207c7d94-6f73-406a-af09-6546b8d65dd5.jpeg</url>
      <title>DEV Community: Paulo Neto</title>
      <link>https://dev.to/pauloneto</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pauloneto"/>
    <language>en</language>
    <item>
      <title>Documentando API's com Springdoc OpenAPI</title>
      <dc:creator>Paulo Neto</dc:creator>
      <pubDate>Wed, 05 Mar 2025 17:50:20 +0000</pubDate>
      <link>https://dev.to/pauloneto/documentando-apis-com-springdoc-openapi-52nf</link>
      <guid>https://dev.to/pauloneto/documentando-apis-com-springdoc-openapi-52nf</guid>
      <description>&lt;p&gt;No mundo do desenvolvimento de software, especialmente no contexto de APIs REST, uma documentação clara e precisa é essencial para garantir a compreensão e a usabilidade das interfaces. No ecossistema Java, a combinação de Spring Docs e Bean Validation oferece uma abordagem robusta e eficaz para construir APIs bem documentadas e com validações automáticas. Neste artigo, vamos explorar as vantagens dessa implementação e como ela pode melhorar a qualidade do seu projeto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentação de APIs REST com Spring Docs
&lt;/h2&gt;

&lt;p&gt;A documentação de uma API é crucial para a interação com outros sistemas e desenvolvedores. Sem uma documentação adequada, pode-se gerar confusão, erros e falta de compreensão sobre o funcionamento da API. O Spring Docs facilita esse processo, especialmente quando se utiliza o Spring Boot para construir suas APIs REST.&lt;/p&gt;

&lt;p&gt;O Spring Docs é frequentemente integrado com o Swagger, uma ferramenta amplamente utilizada para gerar documentação interativa de APIs. A vantagem principal de usar o Spring Docs é a facilidade de configuração e a capacidade de gerar documentação automaticamente a partir do código. Utilizando anotações como @Api, @ApiOperation, @ApiResponse, entre outras, é possível fornecer uma descrição detalhada das rotas, parâmetros, tipos de resposta e erros que a API pode gerar.&lt;/p&gt;

&lt;p&gt;Por exemplo, ao anotar um endpoint da seguinte maneira:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Tag&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"categorias"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"API para ações no recurso Categoria"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"categorias"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CategoriaController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;CategoriaService&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CategoriaController&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;CategoriaService&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Operation&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Cria uma nova Categoria"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Cria uma nova Categoria, caso ela não exista"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"categorias"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@ApiResponses&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; 
            &lt;span class="nd"&gt;@ApiResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Ação realizada com sucesso"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;responseCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"201"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nd"&gt;@Content&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mediaType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
                    &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CategoriaDTO&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;}),&lt;/span&gt;
            &lt;span class="nd"&gt;@ApiResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Categoria exitente"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;responseCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"409"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nd"&gt;@Content&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mediaType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
                    &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Erro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;}),&lt;/span&gt;
            &lt;span class="nd"&gt;@ApiResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Ocorreu um erro interno inesperado na API"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;responseCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"500"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nd"&gt;@Content&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mediaType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
                    &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;@Schema&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Erro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt; 
            &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;produces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MediaType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;APPLICATION_JSON_VALUE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@ResponseStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CREATED&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;CategoriaDTO&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nd"&gt;@Valid&lt;/span&gt; &lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nd"&gt;@NotBlank&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"O nome da nova Categoria não pode ser vazio!"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;novaCategoria&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;novaCategoria&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O Spring Docs, juntamente com o Swagger, gera automaticamente a documentação interativa da API, permitindo que os desenvolvedores testem a API diretamente pela interface. Isso aumenta a produtividade da equipe e reduz a margem de erro, já que a documentação está sempre em sincronia com o código.&lt;br&gt;
Para acessar a documentação gerada, acesse: &lt;code&gt;http://localhost:8080/swagger-ui/index.html&lt;/code&gt;&lt;br&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%2F3uqyfimqf8brej7ykygi.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%2F3uqyfimqf8brej7ykygi.png" alt="Image description" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Validação de Campos com Bean Validation
&lt;/h3&gt;

&lt;p&gt;A validação de dados de entrada é outra prática essencial em APIs RESTful. No Spring, o Bean Validation é uma das ferramentas mais poderosas para garantir que os dados recebidos nos endpoints atendam aos critérios definidos. Isso não só melhora a confiabilidade da aplicação, como também evita a necessidade de escrever validações manuais e repetitivas.&lt;/p&gt;

&lt;p&gt;O Bean Validation utiliza anotações como @NotNull, @Size, @Email, &lt;a class="mentioned-user" href="https://dev.to/min"&gt;@min&lt;/a&gt;, entre outras, que podem ser aplicadas diretamente nas entidades ou nos parâmetros dos métodos. A validação é feita automaticamente pelo Spring antes de os dados serem processados, e caso algum dado inválido seja enviado, o Spring lança uma exceção de erro com uma mensagem clara sobre o que está errado.&lt;/p&gt;

&lt;p&gt;Exemplo de validação utilizando Bean Validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Getter&lt;/span&gt;
&lt;span class="nd"&gt;@Setter&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProdutoRequest&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Serializable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;serialVersionUID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@NotBlank&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"O nome do produto é obrigatório!"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;nome&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@NotBlank&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Uma descrição para o produto é obrigatório!"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;descricao&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Positive&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Um preço para o produto é obrigatório!"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;preco&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@NotNull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Um ID de uma categoria deve se informado!"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;categoriaId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ao utilizar essas anotações, o Spring Boot automaticamente valida os dados e, se houver algum erro, retorna uma resposta 400 (Bad Request) com uma mensagem detalhada sobre qual campo falhou na validação. Isso reduz a carga de trabalho do desenvolvedor e evita que erros de dados inválidos cheguem à camada de serviço ou banco de dados.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vantagens da Implementação
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Melhoria na Manutenção e Produtividade: A documentação automática reduz o tempo gasto criando e mantendo documentações desatualizadas, pois ela é gerada diretamente a partir do código-fonte. As anotações utilizadas no Spring Docs ficam sempre sincronizadas com os endpoints, facilitando futuras modificações e atualizações na API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consistência e Confiabilidade: A validação automática com Bean Validation garante que todos os dados recebidos sejam consistentes e atendam aos requisitos de integridade definidos. Isso minimiza erros humanos e aumenta a confiabilidade da API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Facilidade de Uso para Consumidores da API: A documentação interativa do Swagger proporciona um ambiente onde outros desenvolvedores ou sistemas podem testar a API diretamente, sem necessidade de configurar um cliente separado. Isso acelera a integração e melhora a experiência do consumidor da API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Escalabilidade: Ao combinar validação automática com uma documentação gerada de forma inteligente, o desenvolvedor pode escalar a API de forma mais segura e eficiente, pois as mudanças na API refletem diretamente na documentação e nas validações sem exigir trabalho adicional manual.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Redução de Erros e Melhor Debugging: Quando a validação de dados falha, a API retorna respostas claras e úteis, facilitando a identificação e correção de erros. Isso acelera o ciclo de desenvolvimento e torna mais fácil para os desenvolvedores depurarem a aplicação.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Integrar Spring Docs com Bean Validation em uma API REST é uma prática altamente recomendada para garantir que as APIs sejam bem documentadas e, ao mesmo tempo, seguras. A documentação automática melhora a interação entre desenvolvedores e sistemas, enquanto a validação de campos oferece maior confiança na integridade dos dados. Essa combinação de ferramentas do Spring resulta em uma arquitetura de API mais robusta, escalável e de fácil manutenção. Ao adotar essas práticas, você garante que sua API seja não só eficiente, mas também de fácil uso e integração para outros desenvolvedores.&lt;/p&gt;

&lt;h3&gt;
  
  
  Referências
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://springdoc.org/" rel="noopener noreferrer"&gt;Documentação do Springdocs&lt;/a&gt;&lt;br&gt;
&lt;a href="https://beanvalidation.org/" rel="noopener noreferrer"&gt;Documentação do Bean Validation&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/paulo-neto/event-driven-architecture-case-study/tree/main/ecommerce-product" rel="noopener noreferrer"&gt;Fontes com o exemplo de implementação&lt;/a&gt;&lt;/p&gt;

</description>
      <category>spring</category>
      <category>openapi</category>
      <category>documentation</category>
      <category>programming</category>
    </item>
    <item>
      <title>Protegendo suas API's com Keycloak</title>
      <dc:creator>Paulo Neto</dc:creator>
      <pubDate>Mon, 03 Mar 2025 17:05:47 +0000</pubDate>
      <link>https://dev.to/pauloneto/protegendo-suas-apis-com-keycloak-4mkk</link>
      <guid>https://dev.to/pauloneto/protegendo-suas-apis-com-keycloak-4mkk</guid>
      <description>&lt;p&gt;Dando continuidade ao post que fiz há um tempo sobre representação de arquitetura de &lt;a href="https://dev.to/pauloneto/por-que-representar-a-arquitetura-de-uma-aplicacao-em-diagramas-59ol"&gt;aplicações com diagramas C4&lt;/a&gt;, neste post trago uma questão muito importante quando falamos sobre desenvolvimento de API's Rest, que é a segurança. Para exemplificar a segurança em API's Rest, trago um exemplo de implementação de um dos componentes da arquitetura representada no diagrama do post anterior. Neste post apresento a &lt;a href="https://github.com/paulo-neto/event-driven-architecture-case-study/tree/main/ecommerce-product" rel="noopener noreferrer"&gt;implementação do microservice &lt;code&gt;ecommerce-ms-product&lt;/code&gt;&lt;/a&gt; implementado com Java 17, JPA e Spring Boot. Para a segurança da API utilizei o Keycloak como Identity and Access Management(IAM).&lt;/p&gt;

&lt;h3&gt;
  
  
  O que é um Identity and Access Management(IAM)?
&lt;/h3&gt;

&lt;p&gt;A gestão de identidade e acesso oferece supervisão sobre a autenticação de usuários e a disponibilidade de recursos. Frequentemente referida como IAM, essa tecnologia assegura que as pessoas adequadas tenham acesso aos recursos digitais apropriados no momento oportuno e pelas razões corretas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conceitos básicos sobre IAM
&lt;/h3&gt;

&lt;p&gt;Um &lt;em&gt;&lt;strong&gt;recurso digital&lt;/strong&gt;&lt;/em&gt; refere-se a qualquer combinação de aplicativos e dados dentro de um sistema computacional. Entre os exemplos, estão aplicativos web, APIs, plataformas, dispositivos e bancos de dados.&lt;/p&gt;

&lt;p&gt;No centro do IAM está a &lt;em&gt;&lt;strong&gt;identidade&lt;/strong&gt;&lt;/em&gt;. Alguém deseja acessar seu recurso, seja um cliente, funcionário, membro ou participante. No contexto do IAM, uma conta de usuário corresponde a uma &lt;em&gt;&lt;strong&gt;identidade digital&lt;/strong&gt;&lt;/em&gt;. Além de representar pessoas, essas contas também podem estar associadas a elementos não humanos, como softwares, dispositivos da Internet das Coisas (IoT) ou robôs.&lt;/p&gt;

&lt;p&gt;A &lt;em&gt;&lt;strong&gt;autenticação&lt;/strong&gt;&lt;/em&gt; consiste na verificação de uma identidade digital, garantindo que alguém (ou algo) comprove ser quem afirma.&lt;br&gt;
Já a &lt;em&gt;&lt;strong&gt;autorização&lt;/strong&gt;&lt;/em&gt; determina quais recursos um usuário tem permissão para acessar.&lt;/p&gt;

&lt;p&gt;Os padrões de autenticação e autorização são essenciais para garantir a segurança e a eficiência dos sistemas de gerenciamento de identidade (IAM). Os mais reconhecidos e amplamente adotados na indústria incluem:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;OAuth 2.0&lt;/strong&gt;&lt;/em&gt;: Protocolo de autorização que permite acesso seguro a recursos sem expor credenciais.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;OpenID Connect (OIDC)&lt;/strong&gt;&lt;/em&gt;: Extensão do OAuth 2.0 para autenticação, permitindo que usuários se identifiquem de maneira segura.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;SAML (Security Assertion Markup Language)&lt;/strong&gt;&lt;/em&gt;: Protocolo baseado em XML usado para autenticação e troca de informações de identidade entre diferentes sistemas.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;FIDO2/WebAuthn&lt;/strong&gt;&lt;/em&gt;: Padrão para autenticação sem senha, baseado em chaves criptográficas.&lt;br&gt;
&lt;em&gt;&lt;strong&gt;LDAP (Lightweight Directory Access Protocol)&lt;/strong&gt;&lt;/em&gt;: Protocolo usado para acessar e manter diretórios de informações sobre usuários e dispositivos.&lt;/p&gt;

&lt;p&gt;Esses padrões são amplamente considerados os mais seguros e confiáveis para gerenciar identidades, proteger dados pessoais e controlar o acesso a recursos.&lt;/p&gt;
&lt;h3&gt;
  
  
  Client Credentials Flow
&lt;/h3&gt;

&lt;p&gt;Na implementação utilizei o Client Credentials Flow(definido no &lt;a href="https://tools.ietf.org/html/rfc6749#section-4.4" rel="noopener noreferrer"&gt;OAuth 2.0 RFC 6749&lt;/a&gt;, seção 4.4) envolve um aplicativo trocando suas credenciais de aplicativo, como client ID e client secret, por um token de acesso.&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%2Flh8xbq4hzjva3al82rir.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%2Flh8xbq4hzjva3al82rir.png" alt="Image description" width="800" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Este fluxo é mais adequado para aplicativos Máquina-a-Máquina (M2M), como CLIs, daemons ou serviços de backend, porque o sistema deve autenticar e autorizar o aplicativo em vez de um usuário.&lt;/p&gt;
&lt;h3&gt;
  
  
  Na prática
&lt;/h3&gt;

&lt;p&gt;Na implementação do microservice &lt;em&gt;ecommerce-ms-product&lt;/em&gt; criei duas coleções de recurso:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/produtos&lt;br&gt;
/categorias&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Nelas implementei endpoints para criar, consultar deletar e atualizar esses recursos. Para proteger esses endpoints, para que sejam acessados apenas por quem tem as credenciais necessárias, adicionei no aquivo &lt;code&gt;application.properties&lt;/code&gt; do projeto a seguinte entrada:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/security-ecommerce-api&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Essa entrada configura a aplicação para utilizar o realm &lt;code&gt;security-ecommerce-api&lt;/code&gt; que foi criado no Keycloak&lt;/p&gt;

&lt;p&gt;Também adicionei as seguintes dependências do Spring no projeto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.security&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-security-config&amp;lt;/artifactId&amp;gt;
        &amp;lt;/dependency&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;spring-boot-starter-oauth2-resource-server&amp;lt;/artifactId&amp;gt;
        &amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com essas dependências no projeto, os endpoints do projeto já ficam indisponíveis, sendo acessadas apenas por quem tem as credenciais adequadas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuração do Realm security-ecommerce-api
&lt;/h2&gt;

&lt;p&gt;Na área interna do Keycloak, crie um novo Realm:&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%2Fjdtl1hj2anmv7z9ie7nj.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%2Fjdtl1hj2anmv7z9ie7nj.png" alt="Image description" width="400" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No Realm criado, vá ao menu Clients e crie um novo Client:&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%2Fujt163apyokgv0jeadni.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%2Fujt163apyokgv0jeadni.png" alt="Image description" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No nosso exemplo, criei o client ID &lt;code&gt;ecommerce-client&lt;/code&gt;, o client secret é gerado pelo Keycloak e apresentado na aba Credentials:&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%2F4kxh0d5f7bgkk55a0d1h.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%2F4kxh0d5f7bgkk55a0d1h.png" alt="Image description" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Testando a API segura
&lt;/h3&gt;

&lt;p&gt;Para testar essa implementação, utilizei uma extensão do VSCode, &lt;a href="https://marketplace.visualstudio.com/items?itemName=humao.rest-client" rel="noopener noreferrer"&gt;Rest Client&lt;/a&gt; para executar requisições:&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%2Fvxmbhyejpjj68rhezqfw.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%2Fvxmbhyejpjj68rhezqfw.png" alt="Image description" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuração para Keycloak local
&lt;/h2&gt;

&lt;p&gt;Para rodar o Keycloak local utilizei o Docker e Compose para subir os serviços do Mysql, para a base de dados do Keycloak, e do Keycloak.&lt;br&gt;
&lt;/p&gt;

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

  mysqlsrv:
    image: mysql:5.7
    environment:
        MYSQL_ROOT_PASSWORD: "MySql2019!"
        MYSQL_DATABASE: "ecommerce-produtos"
    ports:
      - "3306:3306"
    volumes:
      - /home/paulo/Desenvolvimento/docker/mysql:/var/lib/mysql
    networks:
      - ecommerce-network

  keycloak:
    image: quay.io/keycloak/keycloak:24.0
    container_name: keycloak-ecommerce
    environment:
      KC_HOSTNAME: localhost
      KC_HOSTNAME_PORT: 7080
      KC_HOSTNAME_STRICT_BACKCHANNEL: "true"
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
      KC_HEALTH_ENABLED: "true"
      KC_LOG_LEVEL: info
      KC_DB: mysql
      KC_DB_USERNAME: root
      KC_DB_PASSWORD: MySql2019!
      KC_DB_URL_HOST: mysqlsrv
      KC_DB_URL_PORT: 3306
      KC_DB_SCHEMA: keycloak
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:7080/health/ready"]
      interval: 15s
      timeout: 2s
      retries: 15
    command: ["start-dev", "--http-port", "7080", "--https-port", "7443"]
    ports:
      - "7080:7080"
      - "7443:7443"
    networks:
      - ecommerce-network
    depends_on: 
      - mysqlsrv

networks: 
  ecommerce-network:
    driver: bridge  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>api</category>
      <category>springsecurity</category>
      <category>springboot</category>
      <category>iam</category>
    </item>
    <item>
      <title>Por que representar a arquitetura de uma aplicação em diagramas?</title>
      <dc:creator>Paulo Neto</dc:creator>
      <pubDate>Thu, 16 Nov 2023 00:33:58 +0000</pubDate>
      <link>https://dev.to/pauloneto/por-que-representar-a-arquitetura-de-uma-aplicacao-em-diagramas-59ol</link>
      <guid>https://dev.to/pauloneto/por-que-representar-a-arquitetura-de-uma-aplicacao-em-diagramas-59ol</guid>
      <description>&lt;p&gt;Você já pediu a um desenvolvedor para ele explicar como funciona o software que ele desenvolveu/desenvolve? Provavelmente a resposta vai conter algum rabisco de caixinhas e setas, na tentativa de representar visualmente como os componentes do software em questão se comunicam.&lt;/p&gt;

&lt;p&gt;Na industria de software existem algumas notações conhecidas para representar a arquitetura de um software, como Linguagem de Modelagem Unificada (UML), o ArchiMate e o SysML. Porém na tentativa de "agilizar" a entrega, muitas equipes descartam essas notações e utilizam "caixinhas e setas" mais simples, na tentativa de representar a arquitetura do software. Mas será que vale descartar essas notações? Será que as equipes de desenvolvimento ainda conseguem representar bem a arquitetua?&lt;/p&gt;

&lt;h2&gt;
  
  
  Diagramas C4
&lt;/h2&gt;

&lt;p&gt;O modelo C4 foi criado como uma forma de ajudar as equipes de desenvolvimento de software a descrever e comunicar a arquitetura de software, tanto durante sessões iniciais de design quanto ao documentar retrospectivamente uma base de código existente. Com ele podemos representar em diversos níveis de detalhe como uma aplicação funciona. &lt;br&gt;
O modelo C4 sugere 4 níveis visões de diagramas para a representação de um software:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nível 1, System Context: Visão de como o a aplicação se comunica com mundo ao seu redor.&lt;/li&gt;
&lt;li&gt;Nível 2, Container: Amplia o escopo da aplicação, mostrando os blocos de construção técnicos de alto nível.&lt;/li&gt;
&lt;li&gt;Nível 3, Component: Amplia um contêiner individual, mostrando os componentes dentro dele.&lt;/li&gt;
&lt;li&gt;Nível 4, Code: Um diagrama de código, class UML por exemplo, pode ser usado para ampliar um componente individual, mostrando como esse componente é implementado.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nesse Post apresento um exemplo do diagrama Nível 3 Component e como escrever utilizando o &lt;a href="https://github.com/plantuml-stdlib/C4-PlantUML/tree/master" rel="noopener noreferrer"&gt;PlantUml&lt;/a&gt;, em outras postagens trarei exemplos dos demais níveis.&lt;/p&gt;
&lt;h3&gt;
  
  
  Contexto da Aplicação
&lt;/h3&gt;

&lt;p&gt;A aplicação em questão é um E-commerce fictício, e o diagrama de Componentes apresenta a responsabilidade de cada um dos componentes da aplicação e suas integrações.&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%2Fkjgxy1kw9ptlsde28n5l.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%2Fkjgxy1kw9ptlsde28n5l.png" alt="Image description" width="800" height="899"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  C-4 PlantUML
&lt;/h3&gt;

&lt;p&gt;Para criar esse diagrama utilizei o C-4 PlantUml que fornece uma forma simples de criar diagras utilizando a sintaxe do PlantUml, segue o código desse diagrama&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@startuml
title &amp;lt;size:45&amp;gt;C4 Component diagram to Generic E-Commerce
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
!define DEVICONS https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons
!define DEVICONS2 https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/devicons2
!define SPRITESURL https://raw.githubusercontent.com/rabelenda/cicon-plantuml-sprites/v1.0/sprites
!define FONTAWESOME https://raw.githubusercontent.com/tupadr3/plantuml-icon-font-sprites/master/font-awesome-5
!includeurl SPRITESURL/kafka.puml
!includeurl SPRITESURL/cassandra.puml
!includeurl SPRITESURL/redis.puml
!include DEVICONS2/nestjs.puml
!include DEVICONS/angular.puml
!include DEVICONS/java.puml
!include DEVICONS/nodejs.puml
!include DEVICONS/mysql.puml
!include DEVICONS/redis.puml

!include FONTAWESOME/users.puml



Person(customer, "E-commerce customer", "Customer who wants to buy a product or service")


System_Boundary(core, "E-commerce Component core "){
    Container(product,"API ecommerce-ms-product", "Java - Quarkus", $sprite="java")
    Container(order,"API ecommerce-ms-order", "Java - Quarkus", $sprite="java")
    ContainerDb(product_db,"Database","MySql", "Holds product information", $sprite="cassandra")
    ContainerDb(order_db,"Database","Cassandra", "Holds order and invoice information", $sprite="mysql")
    ContainerDb(redisDB,"Memory Database","Redis","Memory database for search itens, cart itens, order and invoice information", $sprite="redis")
    Container(email, "API ecommerce-ms-email", "Java - Quarkus", "Service for email send", $sprite="java")

    System_Boundary(sale, "Sale components"){
        ContainerQueue(orderTopic,"TOPIC", "Kafka", "Topic for new order event", $sprite="kafka")
        Container(orderConsumer, "Consumer", "Nestjs", "Consumer of new order event", $sprite="nestjs")
        ContainerQueue(nfseTopic,"TOPIC", "Kafka", "Topic for issuing invoices event", $sprite="kafka")
        Container(nfseConsumer, "Consumer", "Nestjs", "Consumer of invoice issuance events", $sprite="nestjs")
    }
}

System_Boundary(app, "E-commerce Aplication "){
    Container(spa, "SPA", "Angular", "The main interface that the customer interacts with", $sprite="angular")
    Container(bff,"BFF","NodeJs","Backend to interface SPA", $sprite="nodejs")

}

System_Boundary(external, "External components"){
    Component_Ext(payment,"API", "Payment service")
    Component_Ext(nfse,"API", "NFS-e service")
}

Rel_Neighbor(customer,spa,"Uses","https")

Rel_R(spa,bff,"Use","https")
Rel(product,product_db, "Read/Write", "tcp")
Rel(order,order_db, "Read/Write", "tcp")

Rel_D(bff,product,"List products","https")
Rel(bff, orderTopic, "New order event", "tcp")
Rel(bff,redisDB, "Save cart itens, search", "http")
Rel_R(orderTopic, orderConsumer,"Order process","tcp")
Rel(orderConsumer,payment, "Confirm payment?", "https")
Rel(orderConsumer,order, "Save new order", "https")
Rel(orderConsumer,product,"Decreases inventory of sale products", "https")
Rel(orderConsumer,nfseTopic, "Confirmed payment, issue invoice event", "tcp")
Rel(nfseTopic,nfseConsumer, "Consumes","tcp")
Rel(nfseConsumer, nfse, "Uses", "https")
Rel(orderConsumer,email, "Send purchase confirmation email", "https")

@enduml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma representação bem feita dos componentes de uma aplicação e suas integrações pode te polpar algumas horas de reuniões na sua semana.&lt;/p&gt;

&lt;p&gt;Para saber mais sobre o &lt;a href="https://c4model.com/" rel="noopener noreferrer"&gt;C-4 Model&lt;/a&gt;&lt;br&gt;
Sobre a sintaxe do &lt;a href="https://github.com/plantuml-stdlib/C4-PlantUML" rel="noopener noreferrer"&gt;C-4 PlantUML&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Plugin do &lt;a href="https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml" rel="noopener noreferrer"&gt;VSCode para o PlantUML&lt;/a&gt;&lt;/p&gt;

</description>
      <category>c4</category>
      <category>components</category>
      <category>architecture</category>
      <category>plantuml</category>
    </item>
  </channel>
</rss>
