Implementando o uso de contratos na API
Dando sequência num projeto iniciado na aula da Etec Itu, achei interessante criar uma explicação sobre o uso de contratos na nossa API.
Vimos na aula anterior o uso do Swagger para criarmos a documentação da API, agora daremos um pouco mais de profissionalismo para a aplicação.
Para começar, vamos falar sobre o que são “Contratos” ?
Um contrato de API é um documento que é um acordo entre equipes diferentes sobre como a API é projetada. Atualmente, a forma mais comum de contrato de API é uma Especificação OpenAPI, um formato de descrição neutro, portátil e aberto, que padroniza como as APIs REST são descritas. O Swagger é um conjunto de ferramentas de código aberto criadas em torno da Especificação OpenAPI que podem ajudá-lo a projetar, criar, documentar e consumir APIs REST.
Mas de quem é a responsabilidade de definir e especificar um Contrato de Api ? Isto pode variar de empresa para empresa, mas normalmente caso esteja criando uma API do zero, é comum que a equipe de desenvolvimento realize a especificação, já em casos onde a API nasce para integração entre um ou mais sistemas, é comum que as partes entrem em um acordo sobre a melhor forma de criá-la para que atenda à ambas as equipes.
Então para quem nunca utilizou, como podemos definir um contrato?
Para quem ainda caminha neste mundo das API´S, segue uma visão de um exemplo de uma API de cadastro de Produtos, onde o método POST requer o preenchimento de um objeto:
Vocês podem notar que este objeto, possui campos que não fazem sentido para quem está integrando uma informação:
- o campo da chave primária não deve ser enviado no método POST que faz a inclusão, já que a mesma é gerada pela API.
- o campo da data da inclusão não deveria ser enviado também, já que a própria API irá inserir a data conforme a hora em que foi enviado.
- já o campo da data da alteração menos ainda, pois o mesmo só será preenchido ao utilizar um método de alteração.
Então é aí que entram os contratos ! Devemos criar uma pasta no projeto para armazenarmos os contratos, normalmente criamos uma pasta para os “Request” (requisições enviadas), e o “Response” (respostas da API), então crie as pastas conforme a imagem abaixo, e depois crie uma classe na pasta “Requests” chamando-a de “ProdutoCreateRequest” (peço calma aos colegas mais ortodoxos, não ficou legal nomear as classes em português, mas o foco aqui é apenas entender o conceito e já tive alunos confusos com nomes em inglês).
Se olharmos a nossa classe de produtos, ela foi construída assim:
[Table("Produtos")]public class Produto{ [Key] public int ProdutoId { get; set; } [MaxLength(100, ErrorMessage = "O campo {0} não pode ultrapassar {1} caracteres")] public string Descricao { get; set; } public UnidadeDeMedidaEnum UnidadeDeMedida { get; set; } public Decimal ValorDeCusto { get; set; } public Decimal MargemDeLucro { get; set; } public Decimal ValorDeVenda { get; set; } public DateTime DataCadastro { get; set; } public DateTime? DataAlteracao { get; set; } public bool Ativo { get; set; }}
Veja que a propriedade UnidadeDeMedida é do tipo “Enum”, abaixo segue o seu código fonte:
public enum UnidadeDeMedidaEnum{ UNDEFINED, CX, UN, MT, KG, DZ}
Criada a classe, devemos então monta-la com os elementos que queremos que sejam enviados pelo consumidor da API para envio de um novo produto.
public class ProdutoCreateResponse{public string Descricao { get; set; }public string UnidadeDeMedida { get; set; }public Decimal ValorDeCusto { get; set; }public Decimal MargemDeLucro { get; set; }public Decimal ValorDeVenda { get; set; }}
Vejam que vários campos da classe produto foram suprimidos, ficando assim muito mais compacto o seu uso.
Outro tópico interessante é o que foi feito com o campo Unidade de Medida, onde o campo passou a ser do tipo String, ao invés de Enum para ser mais fácil de ser identificado pelo consumidor do serviço.
Então é isto, fizemos a classe, porém, ao devolver os resultados para os clientes, temos que fazer uma transformação da classe “Produto” para a classe “ProdutoCreateResponse”, então podemos fazer de várias formas distintas:
Com utilização de métodos (podendo ser um método de Extensão da classe produto), ou com um mapeamento através do AutoMapper, ou simplesmente copiando propriedade por propriedade para a nova classe.
Exemplo com Extension Methods
Criando um extension method (vide artigo), você pode extender a classe “Produto” e implementar um método que devolva um objeto do tipo “ProdutoCreateResponse”.
public static ProdutoCreateResponse ConverteParaResponse(this Produto prod)
{
ProdutoCreateResponse response = new ProdutoCreateResponse();
response.Descricao = prod.Descricao;
response.MargemDeLucro = prod.MargemDeLucro;
response.UnidadeDeMedida = prod.UnidadeDeMedida.ToString();
response.ValorDeCusto = prod.ValorDeCusto;
response.ValorDeVenda = prod.ValorDeVenda;
return response;
}
Depois de criar o “Extension Method”, agora é só utilizá-lo na nossa controller, conforme demonstrado abaixo:
Exemplo com AutoMapper
Instale a biblioteca do Automapper, através do Nuget Package Manager ou pela linha de comando no Package manager Console:
Install-Package AutoMapper
Tendo instalado o Automapper, é necessário criar o mapeamento “de-para” das classes que deseja utilizar, no arquivo startup.cs, você deve incluir o mapeamento.
Neste projeto que é algo simples, fizemos no startup, mas o correto é criarmos uma classe específica para este mapeamento para que deixemos o Startup mais clean e que a manutenção do projeto fique mais limpa.
Agora é só chamar o Automapper na nossa controller.
Portanto agora é só escolher a melhor forma de utilização e seguir em frente, nem tudo são flores, sempre temos situações específicas que irão requerer configurações diferentes do padrão, mas para isto deixo aqui as referências para que possam aprofundar-se mais nesse assunto.
Gostou do artigo? clique no ícone👏e me siga para ver as próximas publicações !!