Cell CMS — Design Patterns e Endpoints
Tags: softwaredevelopment, csharp, mediatr, development
Published: 2020-06-24 23:59:02

---
title: Cell CMS — Design Patterns e Endpoints
published: true
date: 2020-03-28 14:46:00 UTC
tags: softwaredevelopment,csharp,mediatr,development
canonical_url: 
series: CellCms
---

### Cell CMS — Design Patterns e Endpoints

![](https://cdn-images-1.medium.com/max/1024/0*xH6VW1umEnEzRAaS)<figcaption>Photo by <a href="https://unsplash.com/@ninjason?utm_source=medium&amp;utm_medium=referral">Jason Leung</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption>

No [último post](https://dev.to/ardc_overflow/cell-cms-persistindo-dados-ajm-temp-slug-5382913) 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:

1. **Model** : Camada de persistência dos dados, onde a informação será armazenada
2. **Controller** : Camada onde será feito o processamento dos comandos, sejam eles oriundos das View ou Models, e encaminhamento dos resultados
3. **View** : Camada de apresentação dos dados, onde a informação será exibida ao usuário

Recomendo a leitura do próprio [artigo na Wikipedia](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)para 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](https://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:

1. Controllers: Receberá as **inputs e** emitará **outputs** através de **JSON e HTTP**
2. 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.

![](https://cdn-images-1.medium.com/max/530/1*la_5oEaqRMi9EmqirhbbaA.png)<figcaption>Endpoints, no padrão MVC</figcaption>

![](https://cdn-images-1.medium.com/max/555/1*NraMYOCk1Kqi8EOMmp-qag.png)<figcaption>Endpoints, ainda com Models e Controllers mas utilizando Commands. Queries e Handlers.</figcaption>

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<T?>** : 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<IRequest<T>, 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:

1. **CreateFeed** : Conterá o command e a lógica para criar um novo feed
2. **ListAllFeeds** : Conterá a query para listar todos os feeds
3. **UpdateFeed** : Conterá o command e a lógica para atualizar um feed existente
4. **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)

{% gist https://gist.github.com/rodolphocastro/923ba16b18115d9fa7848ac2f73e51d1 %}

Agora vamos criar nosso FeedsController , tendo um método para cada operação também:

{% gist https://gist.github.com/rodolphocastro/2ff12d750bc85c512a53304e31152e75 %}

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 :

<iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%3Fbg%3Drgba%28171%252C%2520184%252C%2520195%252C%25201%29%26t%3Done-dark%26wt%3Dnone%26l%3Dtext%252Fx-csharp%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%2525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3Dpublic%252520class%252520Startup%25250A%25257B%25250A%252520%252520%25252F%25252F%252520Outros%252520m%2525C3%2525A9todos...%25250A%252520%252520%25252F%25252F%252520This%252520method%252520gets%252520called%252520by%252520the%252520runtime.%252520Use%252520this%252520method%252520to%252520add%252520services%252520to%252520the%252520container.%25250A%252520%252520public%252520void%252520ConfigureServices%28IServiceCollection%252520services%29%25250A%252520%252520%25257B%25250A%252520%252520%252520%252520%25252F%25252F%252520...%252520Outras%252520adi%2525C3%2525A7%2525C3%2525B5es%252520aos%252520services%25250A%25250A%252520%252520%252520%252520%25252F%25252F%252520Adicionando%252520os%252520elementos%252520do%252520MediatR%252520%2525C3%2525A0%252520inje%2525C3%2525A7%2525C3%2525A3o%252520de%252520depend%2525C3%2525AAncia%25250A%252520%252520%252520%252520services.AddMediatR%28GetType%28%29.Assembly%29%25253B%25250A%25250A%252520%252520%252520%252520%25252F%25252F%252520..%252520Outras%252520adi%2525C3%2525A7%2525C3%2525B5es%25250A%252520%252520%25257D%25250A%25257D&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2F%3Fbg%3Drgba%28171%25252C%252520184%25252C%252520195%25252C%2525201%29%26t%3Done-dark%26wt%3Dnone%26l%3Dtext%25252Fx-csharp%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%252525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3Dpublic%25252520class%25252520Startup%2525250A%2525257B%2525250A%25252520%25252520%2525252F%2525252F%25252520Outros%25252520m%252525C3%252525A9todos...%2525250A%25252520%25252520%2525252F%2525252F%25252520This%25252520method%25252520gets%25252520called%25252520by%25252520the%25252520runtime.%25252520Use%25252520this%25252520method%25252520to%25252520add%25252520services%25252520to%25252520the%25252520container.%2525250A%25252520%25252520public%25252520void%25252520ConfigureServices%28IServiceCollection%25252520services%29%2525250A%25252520%25252520%2525257B%2525250A%25252520%25252520%25252520%25252520%2525252F%2525252F%25252520...%25252520Outras%25252520adi%252525C3%252525A7%252525C3%252525B5es%25252520aos%25252520services%2525250A%2525250A%25252520%25252520%25252520%25252520%2525252F%2525252F%25252520Adicionando%25252520os%25252520elementos%25252520do%25252520MediatR%25252520%252525C3%252525A0%25252520inje%252525C3%252525A7%252525C3%252525A3o%25252520de%25252520depend%252525C3%252525AAncia%2525250A%25252520%25252520%25252520%25252520services.AddMediatR%28GetType%28%29.Assembly%29%2525253B%2525250A%2525250A%25252520%25252520%25252520%25252520%2525252F%2525252F%25252520..%25252520Outras%25252520adi%252525C3%252525A7%252525C3%252525B5es%2525250A%25252520%25252520%2525257D%2525250A%2525257D&amp;image=https%3A%2F%2Fcarbon.now.sh%2Fstatic%2Fbrand%2Fbanner.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/3dd9e3eaee303db66a5dc06752ee2570/href">https://medium.com/media/3dd9e3eaee303db66a5dc06752ee2570/href</a></iframe>

Finalmente, vamos executar a API e dar uma olhada no nosso Swagger!

![](https://cdn-images-1.medium.com/max/1024/1*tVyhEr9upGAswnjP2TdB9g.gif)<figcaption>Explorando os métodos que criamos para o FeedsController</figcaption>

**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.

![](https://cdn-images-1.medium.com/max/979/1*skMJxZPnuus2rWfkQ1ejWw.png)<figcaption>O erro sendo retornado no Console da API</figcaption>

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:

1. Adicionar o package **Microsoft.AspNetCore.Mvc.NewtonsoftJson**
2. Configurar para que framework **MVC utilize o Newtonsoft**
3. Opcionalmente: Indicar ao **Swashbuckle** para utilizar o **Newtonsoft,** adicionando o package **Swashbuckle.AspNetCore.Newtonsoft**
<iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fcarbon.now.sh%2Fembed%3Fbg%3Drgba%28171%252C%2520184%252C%2520195%252C%25201%29%26t%3Done-dark%26wt%3Dnone%26l%3Dtext%252Fx-csharp%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%2525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3Dpublic%252520class%252520Startup%25250A%25257B%252520%252520%25250A%252520%252520%25252F%25252F%252520This%252520method%252520gets%252520called%252520by%252520the%252520runtime.%252520Use%252520this%252520method%252520to%252520add%252520services%252520to%252520the%252520container.%25250A%252520%252520public%252520void%252520ConfigureServices%28IServiceCollection%252520services%29%25250A%252520%252520%25257B%25250A%25250A%252520%252520%252520%252520%25252F%25252F%252520Adicionando%252520a%252520gera%2525C3%2525A7%2525C3%2525A3o%252520do%252520swagger.json%25250A%252520%252520%252520%252520services%25250A%252520%252520%252520%252520.AddCellSwagger%28_configuration%29%25250A%252520%252520%252520%252520.AddSwaggerGenNewtonsoftSupport%28%29%25253B%252509%25252F%25252F%252520Indicando%252520ao%252520gerador%252520para%252520utilizar%252520o%252520Netwonsoft%25250A%25250A%252520%252520%252520%252520services%25250A%252520%252520%252520%252520%252520%252520.AddControllers%28%29%25250A%252520%252520%252520%252520%252520%252520.AddNewtonsoftJson%28c%252520%25253D%25253E%25250A%252520%252520%252520%252520%252520%252520%25257B%25250A%252520%252520%252520%252520%252520%252520%252520%252520%25252F%25252F%252520Adicionando%252520e%252520configurando%252520o%252520NewtonsoftJson%252520ao%252520MVC%25250A%252520%252520%252520%252520%252520%252520%252520%252520c.SerializerSettings.MaxDepth%252520%25253D%2525204%25253B%25250A%252520%252520%252520%252520%252520%252520%252520%252520c.SerializerSettings.ReferenceLoopHandling%252520%25253D%252520ReferenceLoopHandling.Ignore%25253B%25250A%252520%252520%252520%252520%252520%252520%25257D%29%25253B%25250A%252520%252520%25257D%25250A%25257D&amp;display_name=Carbon&amp;url=https%3A%2F%2Fcarbon.now.sh%2F%3Fbg%3Drgba%28171%25252C%252520184%25252C%252520195%25252C%2525201%29%26t%3Done-dark%26wt%3Dnone%26l%3Dtext%25252Fx-csharp%26ds%3Dtrue%26dsyoff%3D20px%26dsblur%3D68px%26wc%3Dtrue%26wa%3Dtrue%26pv%3D56px%26ph%3D56px%26ln%3Dfalse%26fl%3D1%26fm%3DHack%26fs%3D14px%26lh%3D133%252525%26si%3Dfalse%26es%3D2x%26wm%3Dfalse%26code%3Dpublic%25252520class%25252520Startup%2525250A%2525257B%25252520%25252520%2525250A%25252520%25252520%2525252F%2525252F%25252520This%25252520method%25252520gets%25252520called%25252520by%25252520the%25252520runtime.%25252520Use%25252520this%25252520method%25252520to%25252520add%25252520services%25252520to%25252520the%25252520container.%2525250A%25252520%25252520public%25252520void%25252520ConfigureServices%28IServiceCollection%25252520services%29%2525250A%25252520%25252520%2525257B%2525250A%2525250A%25252520%25252520%25252520%25252520%2525252F%2525252F%25252520Adicionando%25252520a%25252520gera%252525C3%252525A7%252525C3%252525A3o%25252520do%25252520swagger.json%2525250A%25252520%25252520%25252520%25252520services%2525250A%25252520%25252520%25252520%25252520.AddCellSwagger%28_configuration%29%2525250A%25252520%25252520%25252520%25252520.AddSwaggerGenNewtonsoftSupport%28%29%2525253B%25252509%2525252F%2525252F%25252520Indicando%25252520ao%25252520gerador%25252520para%25252520utilizar%25252520o%25252520Netwonsoft%2525250A%2525250A%25252520%25252520%25252520%25252520services%2525250A%25252520%25252520%25252520%25252520%25252520%25252520.AddControllers%28%29%2525250A%25252520%25252520%25252520%25252520%25252520%25252520.AddNewtonsoftJson%28c%25252520%2525253D%2525253E%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%2525257B%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520%2525252F%2525252F%25252520Adicionando%25252520e%25252520configurando%25252520o%25252520NewtonsoftJson%25252520ao%25252520MVC%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520c.SerializerSettings.MaxDepth%25252520%2525253D%252525204%2525253B%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%25252520%25252520c.SerializerSettings.ReferenceLoopHandling%25252520%2525253D%25252520ReferenceLoopHandling.Ignore%2525253B%2525250A%25252520%25252520%25252520%25252520%25252520%25252520%2525257D%29%2525253B%2525250A%25252520%25252520%2525257D%2525250A%2525257D&amp;image=https%3A%2F%2Fcarbon.now.sh%2Fstatic%2Fbrand%2Fbanner.png&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;scroll=auto&amp;schema=carbon" width="1024" height="480" frameborder="0" scrolling="no"><a href="https://medium.com/media/6830982a0f0db0a7b9e250f601fdd3c8/href">https://medium.com/media/6830982a0f0db0a7b9e250f601fdd3c8/href</a></iframe>

**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!