4 Recomendações Práticas para Projetos de Microsserviços em .NET

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?

Compartilhe este insight:

12 respostas

  1. Olá Elemar tudo certo? gostei do exemplo! podia continuar com a serie. Uma Dúvida aqui clientes.application não seria “melhor” clientes.domain?

    1. Prefiro “application” para me manter fiel a referência do modelo proposto por Cockburn. De qualquer forma, o domínio está incluso ali sim.

  2. 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?

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

      1. 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?

  3. 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!!

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Elemar Júnior

Sou fundador e CEO da EximiaCo e atuo como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mais insights para o seu negócio

Veja mais alguns estudos e reflexões que podem gerar alguns insights para o seu negócio:

I have no idea of how many times I had to write a function do discover the minimum and the...
Sometimes it is not enough to know if two strings are equals or not. Instead, we need to get an...
Nesse ano, palestrei na APIX sobre microsserviços. Abaixo, registro em vídeo feito pela organização do evento. Comentários? Feedback?
To write parallel code is not a trivial task. There are details to consider and rules to observe. Could you...
Recebi um bocado de feedback positivo para minhas palestras no Devxperience deste ano. Muita gente mandou e-mails solicitando, principalmente, os...
I worked a lot in the last months updating the RavenDB bootcamp to v4.x. My work is done (for a...