Cell CMS — Design Patterns e Endpoints
No último post falamos sobre persistência de dados, configuramos os models utilizando o EntityFrameworkCore e ficamos a um último passo de ter alguma coisa rodando! No post de hoje vamos falar brevemente sobre como estruturar nossos endpoints , mediator design pattern e CQRS (Command/Query Responsability Segregation).
Começaremos um novo branch feature/endpoints para o que for implementado durante este post. Este branch já contem alguns ajustes de features feitas fora de posts: feature/refactor-context e feature/limpeza-codigo. Vamos ao conteúdo!
Uma breve história sobre Patterns
Um assunto bem clássico do universo de TI são os design patterns. Um assunto vasto e que não seria possível abordar em um simples post, talvez em uma série de posts com vastos exemplos para não tornar a leitura entediante!
Para quem já trabalhou com tecnologias web o termo MVC soará familiar. Essa sigla significa Model-View-Controller e é um dos design patterns mais comuns! Em antigas versões, ainda no .NET Framework, o próprio “apelido” do framework era ASPNET MVC. Basicamente o MVC divide a separação em três camadas lógicas:
- Model : Camada de persistência dos dados, onde a informação será armazenada
- Controller : Camada onde será feito o processamento dos comandos, sejam eles oriundos das View ou Models, e encaminhamento dos resultados
- View : Camada de apresentação dos dados, onde a informação será exibida ao usuário
Recomendo a leitura do próprio artigo na Wikipediapara quem quiser mais referencial teórico.
Este pattern funciona muito bem, porém ele foi sendo complementado e evoluído com o tempo.
Por exemplo, no universo .NET, é muito comum ver projetos com ViewModels , uma camada adicional para ajustar os dados para a exibição ao usuário. A biblioteca AutoMapper é, até hoje, uma das mais baixadas por causa de DTOs e ViewModels!
Outro recurso interessante (e bem visual) é o Refactoring Guru, onde são apresentados diversos tipos de Patterns com explicação gráfica, snippets e tudo! Se você for mais da literatura clássica a recomendação é o Gang of Four (GOF) Design Patterns!
CQRS
Nossa API, por definição, não possuirá Views. Temos duas camadas principais:
- Controllers: Receberá as inputs e emitará outputs através de JSON e HTTP
- Models: Persistência dos dados , através do EntityFrameworkCore
Cada C ontroller irá conter, no mínimo, as operações CRUD (Create, Read, Update e Delete) para um model. Cada operação será , necessáriamente, um endpoint.
Podemos dizer então que cada controller realizará Commands ou Queries sobre as entidades que eles são responsáveis. Commands são operações que alteram o estado da aplicação , como criações, atualizações e remoções. Queries são operações que não alteram o estado da aplicação , não importa o quão completas sejam. Os Commands e as Queries serão processadas por Handlers específicos, podendo passar por Pipelines pré ou pós execução.
Vamos ver como isso ficaria, abaixo estão dois diagramas. Um pensando no MVC e outro pensando no CQRS.
Como toda abordagem, existem suas vantagens e desvantagens para cada um dos padrões. Eu, pessoalmente, sigo a seguinte lógica:
- MVC: Projetos pequenos , que provavelmente não serão mantidos, e protótipos.
- CQRS : Projetos que podem expandir ou com complexidade mais alta de regras de negócio.
“Mas Rodolpho, o Cell CMS será um projeto com complexidade alta?”
Não! Mas é um projeto que pretendo seguir dando manutenção e gostaria que o código fosse o mais limpo possível para que qualquer pessoa possa olhar o código e entender o que está acontecendo.
Criando nossos endpoints
Antes de começarmos com o CQRS vamos instalar uma biblioteca que nos auxiliará a manter o Mediator Pattern (que se encaixa perfeitamente com o CQRS). Esta biblioteca é a MediatR e a MediatR.Extensions.Microsoft.DependencyInjection . Portanto abra a solution, clique direito na API, Manage NuGet Packages e instale-os!
Antes de configurarmos, vamos ver as duas Interfaces mais importantes do MediatR:
- IRequest : Identifica que a classe é uma requisição. O tipo T é o possível retorno, caso tenha, da requisição. Nossos Commands e Queries implementarão esta interface.
- IRequestHandler, T?> : Identifica que a classe é um Handler para uma Requisição que, caso tenha retorno, retorna um objeto do tipo T. Nossos Handlers implementarão esta interface.
Com isso fora do caminho vamos organizar nossas operações em Commands, Requests e Model afetado:
- Feed : criar novo, atualizar existente, ler todos, deletar
- Tag : criar nova, atualizar existente, ler por feed, ler todas, deletar
- Content : criar novo, atualizar existente, ler por feed, ler por tag, deletar
Para manter o post curto utilizarei como exemplo as operações do Feed! Porém, no GitHub, estão disponíveis as implementações para todos os outros.
Para começar vamos criar duas novas pastas na Api: Features e uma subpasta Feed. Dentro da pasta feed vamos criar 5 arquivos, um para cada command/query:
- CreateFeed : Conterá o command e a lógica para criar um novo feed
- ListAllFeeds : Conterá a query para listar todos os feeds
- UpdateFeed : Conterá o command e a lógica para atualizar um feed existente
- DeleteFeed: Conterá o command e a lógica para deletar um feed existente
O código das 5 classes está disponível abaixo. Para manter a API com poucos arquivos e facilitar a navegação estou colocando o Handler de cada IRequest no mesmo arquivo. Isso manterá o código mais fácil de navegar, ao custo de repetir alguns trechos de código (especialmente no construtor e, futuramente, com o Polly)
Agora vamos criar nosso FeedsController , tendo um método para cada operação também:
Note que podemos utilizar os attributes [FromRoute] e {FromBody] para que o próprio framework mapeie o command/query para nós, sem precisarmos adicionar vários parâmetros!
Antes de executar a API precisamos fazer mais uma configuração: Adicionar o MediatR à injeção de dependência. Podemos fazer isso dentro do nosso Startup.cs :
https://medium.com/media/3dd9e3eaee303db66a5dc06752ee2570/href
Finalmente, vamos executar a API e dar uma olhada no nosso Swagger!
Problemas de Serialização
Para os feeds não tivemos nenhum problema ao serializar nossos json. Mas, ao seguirmos para tags e contents, acabaremos com um problema, ao tentar retornar tags, por exemplo: System.Text.Json.JsonException: A possible object cycle was detected which is not supported.
Este erro ocorre pois ao retornarmos uma Tag ela referencia o Feed ao qual pertence que, então, referencias suas Tags … E assim vamos. O serializador padrão do .NET Core 3.1, System.Text.Json , detecta isso e impede a serialização. Porém, para quem já utilizou as versões anteriores do .NET Core, existe um outro serializador capaz de lidar com estes loops: Newtonsoft.Json.
Para adicionar o Netwonsoft.Json a um projeto .NET Core 3.1 precisamos:
- Adicionar o package Microsoft.AspNetCore.Mvc.NewtonsoftJson
- Configurar para que framework MVC utilize o Newtonsoft
- Opcionalmente: Indicar ao Swashbuckle para utilizar o Newtonsoft, adicionando o package Swashbuckle.AspNetCore.Newtonsoft https://medium.com/media/6830982a0f0db0a7b9e250f601fdd3c8/href
Considerações finais
No próximo post irei fazer um refactor no nosso código adicionando duas novas bibliotecas: AutoMapper e FluentValidations. O AutoMapper nos auxiliará a converter os Commands e Queries para nossos Models e o FluentValidation nos auxiliará a realizar a validação dos Commands e Queries através de uma api Fluent.
Obrigado por lerem mais este post e até a próxima! Abraços!
Top comments (0)