Neste post, gostaria de compartilhar a estrutura que venho adotando em meus projetos com microsserviços. São algumas ideias que tenho adotado com êxito e que podem ajudar você a ter mais sucesso em seus próprios projetos.
Se tiver interesse em entender mais sobre microsserviços, recomendo que acesse o Guia de Conteúdo para Microsserviços deste site.
1. Adote arquitetura hexagonal
Gosto muito do conceito de arqutitetura hexagonal (leia o post relacionado se não está familiarizado com o cenceito) e, para microsserviços, entendo que o conceito se ajusta perfeitamente.
Seguindo essa ideia, sempre que começo a criar um microsserviço em .NET, crio dois projetos. Um que irá operar conter a aplicação (entenda-se: código do domínio) e outro que irá expor essa aplicação para meios externos (geralmente um cliente de mensageria ou uma web api)
2. Organize seu código em Feature folders
Nunca me senti confortável com a separação de código em pastas técnicas (entenda-se Controllers, Views e Models). No lugar disso, prefiro organizar meu código por Features.
A idéia é manter Controller, Views, InputModels e ViewModels todos em uma única pasta agrupados pela feature que está sendo implementada.
Asp.Net Core é suficientemente inteligente para conseguir entender o que está ocorrendo com os Controllers e com as Models automaticamente. O único ajuste necessário é para explicitar onde estão as Views. De qualquer forma, algo simples:
using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Razor; namespace Web.Features { public class FeaturesLocationExpander : IViewLocationExpander { public void PopulateValues(ViewLocationExpanderContext context) { // nothing } public IEnumerable ExpandViewLocations( ViewLocationExpanderContext context, IEnumerable viewLocations) { return new[] { "/Features/{1}/{0}.cshtml", // feature specific content "/Features/Shared/{0}.cshtml" // shared }; } } }
Este código instrui o ASP.net core sobre onde localizar as Views. Para ativar esse código, basta modificar a configuração do Razor.
public void ConfigureServices(IServiceCollection services) { services.AddIdentityApplication(_configuration); // services.AddMvc(); services.Configure<RazorViewEngineOptions>(options => { options.ViewLocationExpanders.Add(new FeaturesLocationExpander()); }); }
3. Identifique claramente as interfaces da aplicação (CommandSide e QuerySide)
Sou adepto a ideia de separar claramente interfaces de comando (que mudam o estado da aplicação) e consulta (que apenas recuperam informações) – entenda-se CQRS.
Essa segregação explícita ajuda a explicar melhor o conceito e tornar tudo mais evidente para quem vai interpretar o código.
4. Adote Swagger
Documentar a API é fundamental. Por isso, defendo a ideia de já começar o projeto usando Swagger. Por convenção, todos os meus microsserviços (apenas API), em seu endereço raiz, redirecionam para a interface Swagger.
using Microsoft.AspNetCore.Mvc; namespace WebApi.Features.Home { public class HomeController : Controller { public IActionResult Index() => new RedirectResult("~/swagger"); } }
Além disso, utilizo os pacotes Swashbuckle.AspNetCore e Swashbuckle.AspNetCore.Examples para fornecer uma experiência mais rica, fornecendo, inclusive, exemplos para dados que precisam ser enviados para o servidor.
[Authorize] [Route("addpf")] [HttpPut] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType(typeof(ClientePF), (int)HttpStatusCode.OK)] [SwaggerRequestExample(typeof(IncluirClientePFCommand), typeof(IncluirClientePFCommandExample))] public async Task<IActionResult> AddPF( [FromBody] IncluirClientePFCommand data, [FromHeader(Name = "x-requestid")] string requestId ) { if (!ModelState.IsValid) { return ModelState.GenerateBadRequestFromExceptions(); } Guid.TryParse(requestId, out var guid); var commandResult = await _mediator.Send(new IdentifiedCommand<IncluirClientePFCommand, ClientePF>( data, guid )); return commandResult != null ? (IActionResult)Ok(commandResult) : BadRequest(); }
Aliás, todos meus projetos de API contem uma pasta dedicada para códigos de exemplo para Swagger.
Concluindo
Sempre afirmo que “São as Cicatrizes que contam a história do guerreiro”. Essas recomendações são oriundas de algumas cicatrizes. Há muitas outras que poderia compartilhar, mas, por agora, gostaria de saber sua opinião. Quais dessas práticas você já adota?
12 respostas
Muito bom o post, sempre aprendo muito.
Olá Elemar tudo certo? gostei do exemplo! podia continuar com a serie. Uma Dúvida aqui clientes.application não seria “melhor” clientes.domain?
Prefiro “application” para me manter fiel a referência do modelo proposto por Cockburn. De qualquer forma, o domínio está incluso ali sim.
Também faço parecido nos meus projetos de Lab essa ideia de identificar CommandSide e QuerySide. Mas o que você acha do seguinte?: Em algumas vezes, experimentei desde o nível de Folders para Projects e até Solutions (de maneira que algumas dependências são colocadas em nuget local), isto é, os folders Aggregates e Commands do exemplo poderiam estar a nível de Project ao invés de Folders – Você acha exagero meu? Ou o projeto deve responder conforme necessário essa separação Solution-Project-Folders?
Em primeiro momento, parece exagerado. Mas, se o projeto for muito grande, concordo plenamente.
Importante deixar claro que, algumas vezes, é necessário criar um projeto de “Contracts” para compartilhamento de interfaces.
Legal! Sim, achei pertinente, então eu havia feito algo assim:
Coloquei como “Contracts” as interfaces dos Commands e Events (no caso a implementação das interfaces Events deixei nos projects que seriam os Aggregates). No caso dos objetos Query e QueryModel deixei em outro project que também representa os contratos do QuerySide. <—Meu raciocínio está certo?
Mestre Elemar, parabéns pela qualidade e obrigado por compartilhar. Não sei quando nem porque comecei o hábito de nomear o Core da app como Application, mas estou feliz em ver que não estou sozinho!!
Elemar, vi que usa o MediatR, ele te ajuda patronizar a projetar no modelo CQRS?
Sim, ajuda bastante. Ele simplifica o encaminhamento de comandos e queries para os “tratadores”