Iniciando com o .NET Aspire

Desenvolver aplicações modernas em .NET frequentemente envolve desafios como configurar microsserviços, gerenciar eventos distribuídos e implementar padrões avançados como CQRS (Command Query Responsibility Segregation). Esses desafios, embora necessários, podem consumir tempo e dificultar o foco no desenvolvimento da lógica de negócio.

.NET Aspire é uma solução que promete simplificar esse processo. Com uma abordagem declarativa, integração nativa com ferramentas como RabbitMQ e bancos de dados, ele reduz a complexidade do setup inicial e permite que você crie aplicações robustas de forma rápida.

Neste artigo, criaremos uma API para gerenciar pedidos como exemplo prático. Porém, é importante destacar que este é apenas um entre muitos cenários possíveis. Com o .NET Aspire, você pode desenvolver desde aplicações de notificações em tempo real até pipelines de eventos para processamento de dados.

Pré-requisitos

Antes de começarmos, certifique-se de que você tenha:

O que é o .NET Aspire?

.NET Aspire é um framework que facilita o desenvolvimento de aplicações modernas em .NET, combinando uma abordagem declarativa com suporte a padrões como CQRS e event sourcing. Ele oferece integração nativa com bancos de dados, mensageria e outros serviços.

Principais Funcionalidades

  • Configuração Declarativa: Use arquivos YAML ou JSON para definir bancos de dados, mensageria e serviços.
  • Padrões Integrados: Suporte nativo a CQRS, event sourcing e APIs REST.
  • Mensageria Nativa: Integração com RabbitMQ, Kafka e outros provedores.
  • Suporte a Bancos de Dados: Compatibilidade com PostgreSQL, SQL Server, MySQL, entre outros.
  • Escalabilidade: Baseado em práticas modernas como o actor model.

Esses recursos permitem que você comece rapidamente sem abrir mão da flexibilidade e do desempenho.

Criando sua Primeira API com .NET Aspire e PostgreSQL

Agora vamos construir uma API para gerenciar pedidos, armazenando-os no PostgreSQL e publicando eventos de confirmação no RabbitMQ.

Configurando o Ambiente

Certifique-se de que o .NET 9 ou superior esteja instalado. Em seguida, crie um novo projeto:

  • Crie um novo projeto de API:
dotnet new webapi -n OrderService  
cd OrderService  
  • Adicione o nuget Aspire necessário para integração com o PostgreSQL:
dotnet add package Aspire.Npgsql.EntityFrameworkCore.PostgreSQL  

Configurando Banco de Dados e RabbitMQ com Docker

Para rodar instâncias do PostgreSQL e RabbitMQ, usaremos o Docker.

  • Crie um arquivo chamado docker-compose.yml na raiz do projeto:
version: '3.9'
services:
  postgres:
    image: postgres:15
    container_name: postgres
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: orders_db
    ports:
      - "5432:5432"
  rabbitmq:
    image: rabbitmq:3-management
    container_name: rabbitmq
    ports:
      - "5672:5672"
      - "15672:15672"
  • Suba os serviços com o comando:
docker-compose up -d

Verifique se os serviços estão ativos:

  • PostgreSQL: Acesse via qualquer cliente de banco de dados na porta 5432.
  • RabbitMQ: Acesse o painel de gerenciamento em http://localhost:15672 (usuário: guest, senha: guest).

Configuração Declarativa

O arquivo aspire.yaml centraliza a configuração da aplicação, permitindo declarar serviços, bancos de dados e mensageria.

  • Crie o arquivo aspire.yaml na raiz do projeto com o seguinte conteúdo:
aspire:
  services:
    - name: OrderService
      type: microservice
  database:
    provider: PostgreSQL
    connectionString: Host=localhost;Database=orders_db;Username=postgres;Password=postgres
  messaging:
    provider: RabbitMQ
    exchange: order_exchange

O que cada campo faz:

  • services: Define o nome e tipo do serviço.
  • database: Configura o banco de dados usado pela aplicação.
  • messaging: Configura o provedor de mensageria e o exchange para publicação de eventos.

Criando os Modelos e Handlers

  • Modelo de Pedido

No diretório Models, crie a classe Order.cs:

public class Order
{
    public Guid Id { get; set; } // Identificador único do pedido
    public string CustomerName { get; set; } // Nome do cliente
    public decimal Amount { get; set; } // Valor total do pedido
    public DateTime CreatedAt { get; set; } // Data de criação
}
  • Configuração do Banco de Dados

No diretório Data, crie a classe ApplicationDbContext:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }

    public DbSet<Order> Orders { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Order>(entity =>
        {
            entity.HasKey(o => o.Id);
            entity.Property(o => o.CustomerName).IsRequired().HasMaxLength(100);
            entity.Property(o => o.Amount).HasPrecision(18, 2);
        });
    }
}

Adicione o contexto no Program.cs:

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
  • Criando o Handler para Processar Pedidos

No diretório Services, crie a classe OrderService:

[Service]
public class OrderService
{
    private readonly ApplicationDbContext _dbContext;
    private readonly IMessagePublisher _messagePublisher;

    public OrderService(ApplicationDbContext dbContext, IMessagePublisher messagePublisher)
    {
        _dbContext = dbContext;
        _messagePublisher = messagePublisher;
    }

    [CommandHandler]
    public async Task HandleOrderAsync(OrderCommand command)
    {
        var order = new Order
        {
            Id = command.OrderId,
            CustomerName = command.CustomerName,
            Amount = command.Amount,
            CreatedAt = DateTime.UtcNow
        };

        _dbContext.Orders.Add(order);
        await _dbContext.SaveChangesAsync();

        var confirmationEvent = new OrderConfirmedEvent
        {
            OrderId = command.OrderId,
            ConfirmationDate = DateTime.UtcNow
        };

        await _messagePublisher.PublishAsync("order_exchange", confirmationEvent);
    }
}
  •  Implementando o Serviço de Mensageria

No diretório Messaging, crie a classe RabbitMqPublisher:

public interface IMessagePublisher
{
    Task PublishAsync(string exchange, object message);
}

public class RabbitMqPublisher : IMessagePublisher
{
    private readonly IConnection _connection;

    public RabbitMqPublisher(IConnectionFactory connectionFactory)
    {
        _connection = connectionFactory.CreateConnection();
    }

    public async Task PublishAsync(string exchange, object message)
    {
        using var channel = _connection.CreateModel();
        channel.ExchangeDeclare(exchange: exchange, type: "fanout");

        var body = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(message);

        await Task.Run(() =>
        {
            channel.BasicPublish(
                exchange: exchange,
                routingKey: "",
                basicProperties: null,
                body: body);
        });
    }
}

Testando a API

Inicie o projeto:

dotnet run  

Envie um pedido para a API usando o Postman ou cURL:

POST /api/orders
{
  "orderId": "b8a4c5f2-9c8b-4d87-8a6d-0cd9f73c4bfa",
  "customerName": "Ana Costa",
  "amount": 120.50
}

Verifique o painel do RabbitMQ em http://localhost:15672 para confirmar a publicação do evento.

Conclusão

O .NET Aspire é uma ferramenta poderosa para desenvolvedores que buscam simplicidade sem abrir mão de práticas modernas. Ao reduzir o trabalho manual e integrar padrões como CQRS e eventos, ele permite que você foque naquilo que importa: entregar valor com rapidez e eficiência.

Com sua abordagem intuitiva e recursos que promovem a produtividade, o .NET Aspire se destaca como uma escolha estratégica para projetos modernos. Ao adotar essa ferramenta, você não apenas otimiza seu fluxo de trabalho, mas também estabelece uma base sólida para aplicações escaláveis, mantendo a qualidade e alinhando-se às melhores práticas de desenvolvimento.

Leave a Comment

O seu endereço de email não será publicado. Campos obrigatórios marcados com *