Como pensar uma arquitetura .NET preparada para a nuvem

Como pensar uma arquitetura .NET preparada para a nuvem

Topicos: Microsoft Azure

Publicado em 22/10/2026 por Admin

A computação em nuvem mudou a forma como desenvolvemos, publicamos e mantemos aplicações. No ecossistema .NET, tecnologias como ASP.NET Core, Docker, Azure Functions, Azure App Service e serviços gerenciados de bases de dados permitem construir sistemas escaláveis sem precisar administrar toda a infraestrutura tradicionalmente associada a uma aplicação empresarial.

Mas existe uma diferença importante entre colocar uma aplicação na nuvem e desenvolver uma aplicação preparada para ela.

Uma API desenvolvida em .NET pode funcionar perfeitamente numa máquina virtual e continuar apresentando problemas quando precisar lidar com picos de tráfego, falhas de rede, múltiplas instâncias, indisponibilidade de dependências ou alterações frequentes de infraestrutura.

Por isso, pensar numa arquitetura .NET preparada para a nuvem exige mais do que escolher um serviço de hospedagem. É necessário considerar como a aplicação será configurada, implantada, escalada, monitorizada e recuperada quando alguma coisa falhar.

Neste artigo, vamos explorar os principais princípios de arquitetura cloud-native aplicados ao .NET, incluindo organização da solução, persistência de dados, comunicação entre serviços, segurança, observabilidade e automação de infraestrutura.

O objetivo é entender como tomar decisões arquiteturais que façam sentido tanto para aplicações novas quanto para sistemas existentes que precisam evoluir.

1. O que significa ter uma arquitetura .NET preparada para a nuvem?

Uma aplicação preparada para a nuvem é projetada para aproveitar as características desse ambiente, incluindo recursos sob demanda, infraestrutura dinâmica, automação, serviços gerenciados e capacidade de recuperação diante de falhas.

Isso não significa necessariamente utilizar microservices, Kubernetes ou dezenas de serviços distribuídos.

Uma aplicação monolítica construída com ASP.NET Core pode ser perfeitamente adequada para a nuvem, desde que sua arquitetura permita configurar, publicar, monitorizar e escalar o sistema de maneira compatível com os requisitos do negócio.

A principal diferença está na forma como a aplicação lida com o ambiente em que é executada.

Numa aplicação tradicional, é comum encontrar configurações associadas diretamente ao servidor, dependências instaladas manualmente, ficheiros armazenados no disco local e processos de publicação que exigem intervenção humana.

Num sistema preparado para a nuvem, procuramos reduzir essas dependências e tornar a execução mais previsível.

Entre os princípios mais importantes estão:

  • Configuração externa ao código-fonte.

  • Separação entre lógica de negócio e infraestrutura.

  • Capacidade de executar múltiplas instâncias.

  • Gestão segura de credenciais e segredos.

  • Tolerância a falhas temporárias.

  • Observabilidade desde o início do projeto.

  • Processos automatizados de build, testes e deployment.

  • Utilização consciente de serviços gerenciados.

  • Gestão adequada de estado e persistência.

Esses princípios ajudam a construir aplicações mais fáceis de operar, independentemente de serem hospedadas no Microsoft Azure, na AWS, no Google Cloud ou numa infraestrutura privada.

A documentação oficial da Microsoft sobre arquitetura cloud-native para .NET aborda precisamente estes temas, incluindo escalabilidade, comunicação, resiliência, segurança e monitorização.

Referência: https://learn.microsoft.com/dotnet/architecture/cloud-native

2. Comece pelos requisitos, não pela tecnologia

Um dos erros mais comuns ao desenhar uma arquitetura para a nuvem é começar pela escolha das ferramentas.

A equipa decide utilizar Kubernetes, divide a aplicação em microservices, introduz um message broker e configura vários serviços gerenciados antes de compreender se essas decisões resolvem problemas reais.

O resultado pode ser uma arquitetura sofisticada, mas difícil de manter e desnecessariamente cara.

Uma abordagem melhor começa pela análise dos requisitos funcionais e não funcionais.

Escalabilidade

Quantos utilizadores a aplicação precisa suportar?

O volume de requisições é relativamente constante ou existem picos durante determinados períodos?

A aplicação realiza operações demoradas, como processamento de documentos, geração de relatórios ou integração com sistemas externos?

As respostas ajudam a determinar se basta aumentar os recursos de uma instância ou se é necessário executar múltiplas instâncias e distribuir o processamento.

Disponibilidade

Qual é o impacto de uma indisponibilidade?

Uma aplicação interna utilizada por uma pequena equipa tem necessidades diferentes de uma plataforma de comércio eletrónico que recebe pedidos continuamente.

A exigência de disponibilidade influencia a estratégia de deployment, a redundância da infraestrutura, a recuperação de dados e a forma como a aplicação reage à falha de dependências.

Latência

Quanto tempo pode demorar uma operação?

Uma API que consulta dados e devolve uma resposta simples tem um perfil diferente de um sistema que processa milhares de registos ou coordena diversas integrações externas.

É importante identificar quais operações precisam de resposta imediata e quais podem ser executadas de forma assíncrona.

Custo operacional

Qual é o orçamento disponível para executar e manter o sistema?

Serviços gerenciados podem reduzir o esforço administrativo, mas os custos dependem do consumo, da configuração, da redundância e das funcionalidades utilizadas.

Uma arquitetura adequada deve considerar não apenas o custo inicial de implementação, mas também os custos recorrentes de infraestrutura, monitorização, armazenamento e manutenção.

Requisitos de segurança

A aplicação manipula dados pessoais, informações financeiras ou documentos confidenciais?

Existem requisitos específicos de autenticação, autorização, auditoria ou retenção de dados?

Essas respostas influenciam as decisões sobre identidade, gestão de segredos, redes, encriptação e acesso às bases de dados.

A arquitetura deve nascer dessas necessidades.

A tecnologia vem depois.

3. Escolha uma arquitetura simples, mas preparada para evoluir

No desenvolvimento .NET, existem várias abordagens arquiteturais possíveis. Entre as mais utilizadas estão a arquitetura em camadas, a Clean Architecture, o monólito modular e os microservices.

A escolha depende do tamanho do sistema, da complexidade do domínio e da forma como a equipa trabalha.

Arquitetura em camadas

Uma aplicação pode ser organizada em camadas de apresentação, aplicação, domínio e infraestrutura.

No ASP.NET Core, isso pode significar separar os controllers ou endpoints da lógica de negócio e do acesso a dados.

É uma abordagem relativamente simples e pode funcionar bem em aplicações pequenas ou médias.

O cuidado necessário é evitar que a separação seja apenas visual. Se todas as regras de negócio acabarem concentradas nos controllers ou espalhadas por serviços sem responsabilidades claras, a solução continuará difícil de manter.

Clean Architecture

A Clean Architecture procura manter as regras de negócio independentes dos detalhes de infraestrutura.

Uma organização possível para uma solução .NET seria:

MinhaAplicacao.sln
│
├── MinhaAplicacao.Api
│   ├── Controllers
│   ├── Endpoints
│   ├── Middleware
│   └── Program.cs
│
├── MinhaAplicacao.Application
│   ├── UseCases
│   ├── DTOs
│   ├── Interfaces
│   └── Validators
│
├── MinhaAplicacao.Domain
│   ├── Entities
│   ├── ValueObjects
│   ├── Enums
│   └── DomainServices
│
└── MinhaAplicacao.Infrastructure
    ├── Persistence
    ├── Repositories
    ├── Messaging
    └── ExternalServices

Essa estrutura é apenas um exemplo. Nem todos os projetos precisam de tantos assemblies, e a organização deve acompanhar a complexidade real da aplicação.

O ponto central é a direção das dependências.

O domínio não deve depender do ASP.NET Core, do Entity Framework Core ou de um fornecedor de cloud específico apenas para implementar as suas regras de negócio.

A camada de aplicação coordena os casos de uso. A infraestrutura implementa os mecanismos técnicos necessários, como persistência e comunicação externa.

Essa separação facilita testes, manutenção e substituição de componentes.

Por exemplo, uma aplicação pode começar utilizando SQL Server e posteriormente migrar parte dos seus dados para outro serviço de persistência. Se as decisões de infraestrutura estiverem devidamente isoladas, a alteração poderá ficar concentrada numa parte menor da solução.

Isso não significa que a migração será automaticamente simples. Diferenças entre bases de dados, transações e modelos de persistência continuam a exigir trabalho. A vantagem está em reduzir o acoplamento desnecessário.

Monólito modular

Para muitas aplicações empresariais, um monólito modular é um excelente ponto de partida.

Nesse modelo, a aplicação continua a ser publicada como uma unidade, mas o código é dividido em módulos com responsabilidades bem definidas.

Por exemplo:

MinhaAplicacao
│
├── Modules
│   ├── Customers
│   ├── Orders
│   ├── Billing
│   └── Notifications
│
├── Shared
│   ├── Authentication
│   └── Observability
│
└── Host
    └── Api

Os módulos podem representar capacidades do negócio, como clientes, encomendas e faturação.

Cada módulo deve ter limites claros, evitando que qualquer parte do sistema aceda indiscriminadamente aos detalhes internos das restantes.

Essa organização permite desenvolver e testar funcionalidades com maior independência, sem introduzir imediatamente a complexidade operacional de um sistema distribuído.

Se, no futuro, existir uma necessidade concreta de separar determinado módulo, os limites arquiteturais já definidos poderão facilitar essa evolução.

A ideia é simples: construir uma aplicação que possa evoluir sem obrigar a equipa a distribuir tudo desde o primeiro dia.

4. Monólito ou microservices: o que faz mais sentido na nuvem?

A nuvem tornou mais acessível a execução de sistemas distribuídos, mas isso não significa que os microservices sejam a melhor escolha para todas as aplicações.

Um monólito bem estruturado pode oferecer desempenho, simplicidade operacional e facilidade de desenvolvimento.

Os microservices, por sua vez, permitem distribuir responsabilidades por serviços independentes, que podem ser desenvolvidos, implantados e escalados separadamente.

A diferença é que essa independência tem um custo.

Quando um monólito é suficiente

Um monólito pode ser a melhor opção quando:

  • A equipa é pequena.

  • O domínio do negócio ainda está a evoluir.

  • Os módulos precisam de transações frequentes entre si.

  • A carga de trabalho não exige escalabilidade independente.

  • A simplicidade de deployment é uma prioridade.

  • Não existem necessidades claras de autonomia entre equipas.

Uma API ASP.NET Core com uma base de dados relacional e deployment automatizado pode atender a muitos destes cenários.

É possível executar várias instâncias da aplicação atrás de um balanceador de carga, desde que o sistema seja preparado para isso.

Quando os microservices começam a fazer sentido

A separação em serviços independentes pode ser vantajosa quando:

  • Existem capacidades de negócio com limites bem definidos.

  • Diferentes partes do sistema precisam de escalar de forma independente.

  • Equipas distintas precisam de autonomia de desenvolvimento e deployment.

  • Determinados componentes possuem requisitos de disponibilidade ou tecnologia diferentes.

  • A organização consegue suportar os custos operacionais de um sistema distribuído.

Por exemplo, um sistema de comércio eletrónico pode ter uma API principal para gerir encomendas e um serviço separado para processar notificações.

Se o envio de notificações não precisar de bloquear a criação de uma encomenda, esse processamento pode ser realizado de forma assíncrona.

No entanto, separar esses componentes em microservices exige considerar comunicação pela rede, autenticação entre serviços, rastreamento distribuído, versionamento de contratos, consistência dos dados e tratamento de falhas.

Não basta criar projetos separados dentro da mesma solução .NET para obter as vantagens de uma arquitetura distribuída.

A regra prática é começar com limites de negócio bem definidos e distribuir os componentes apenas quando os requisitos justificarem essa decisão.

5. Prepare a aplicação para executar em múltiplas instâncias

Uma característica importante de muitas plataformas cloud é a capacidade de aumentar ou reduzir o número de instâncias da aplicação conforme a necessidade.

No ecossistema .NET, uma API ASP.NET Core pode ser executada em múltiplos processos ou containers.

Mas isso exige atenção ao estado da aplicação.

Imagine uma API que armazena os dados de uma sessão em memória.

Na primeira requisição, o utilizador é atendido pela instância A. Na requisição seguinte, o balanceador encaminha o pedido para a instância B.

Se os dados da sessão existirem apenas na memória da instância A, a instância B não terá acesso a esse estado.

Esse tipo de problema pode surgir em diferentes funcionalidades.

Evite depender do estado local

Sempre que possível, mantenha o estado de negócio em sistemas de persistência apropriados.

Isso inclui bases de dados, sistemas de cache distribuída ou outros serviços externos, conforme o tipo de informação.

O estado temporário também merece atenção.

Caches em memória podem ser úteis para otimizar operações, mas não devem ser tratados como armazenamento partilhado entre instâncias.

Se a aplicação precisar de partilhar dados de cache entre vários processos, uma solução distribuída pode ser mais adequada.

Não utilize o disco local como armazenamento permanente

O sistema de ficheiros local pode ser utilizado para operações temporárias, mas não deve ser assumido como armazenamento duradouro ou partilhado.

Em ambientes com containers e instâncias efémeras, um ficheiro pode desaparecer quando a instância é substituída.

Para documentos, imagens e outros ficheiros permanentes, considere serviços de armazenamento de objetos, como o Azure Blob Storage.

Se for necessário partilhar ficheiros entre diferentes componentes, escolha uma solução que ofereça as garantias de acesso, disponibilidade e persistência exigidas pelo sistema.

Atenção às tarefas executadas em segundo plano

Outro problema surge quando cada instância da API executa a mesma tarefa agendada.

Imagine uma aplicação com três instâncias e uma rotina que verifica encomendas pendentes a cada cinco minutos.

Se as três instâncias executarem a mesma rotina sem coordenação, a operação poderá ser processada três vezes.

Para evitar isso, é necessário definir uma estratégia adequada de execução, como um worker dedicado, um serviço de agendamento ou um mecanismo de coordenação distribuída.

A solução depende do tipo de tarefa e das garantias necessárias.

Preparar uma aplicação para escalar significa garantir que o aumento do número de instâncias não altera inesperadamente o comportamento do negócio.

6. Externalize configurações e segredos

Uma aplicação preparada para a nuvem não deve depender de alterações no código para mudar de ambiente.

É necessário distinguir as configurações normais dos segredos.

Entre as configurações normais estão:

  • Endereços de serviços externos.

  • Parâmetros de funcionalidades.

  • Limites de processamento.

  • Intervalos de consulta.

  • Identificadores de ambiente.

Os segredos incluem palavras-passe, chaves de API, credenciais de bases de dados e outros valores que permitem acesso a recursos protegidos.

No .NET, o sistema de configuração do ASP.NET Core permite combinar diferentes fontes, incluindo ficheiros JSON, variáveis de ambiente e fornecedores de configuração específicos.

Um exemplo simples de appsettings.json seria:

{
  "ExternalServices": {
    "CustomerApi": {
      "BaseUrl": "https://api.exemplo.com"
    }
  }
}

A configuração pode ser consumida através do sistema de opções do .NET.

public sealed class CustomerApiOptions
{
    public const string SectionName = "ExternalServices:CustomerApi";

    public string BaseUrl { get; set; } = string.Empty;
}

No Program.cs:

builder.Services
    .AddOptions()
    .BindConfiguration(CustomerApiOptions.SectionName)
    .Validate(
        options => Uri.TryCreate(
            options.BaseUrl,
            UriKind.Absolute,
            out _),
        "A URL da API de clientes é inválida.")
    .ValidateOnStart();

A validação no arranque permite identificar uma configuração inválida antes de a aplicação começar a processar pedidos.

O exemplo pressupõe que a configuração necessária está disponível no ambiente de execução.

Para os segredos, evite guardar credenciais reais no repositório de código ou em ficheiros de configuração versionados.

Durante o desenvolvimento, o User Secrets pode ajudar a manter informações sensíveis fora do projeto. Em produção, utilize um mecanismo apropriado para o ambiente, como o Azure Key Vault e uma identidade gerenciada no Azure.

A aplicação deve obter acesso aos recursos com permissões limitadas, sem depender de credenciais administrativas partilhadas.

Essa abordagem simplifica a promoção entre ambientes e reduz o risco de exposição de informações sensíveis.

7. Utilize serviços gerenciados de forma estratégica

Uma das vantagens da computação em nuvem é a possibilidade de delegar parte da operação da infraestrutura ao fornecedor.

Em vez de instalar e administrar manualmente todos os componentes, a equipa pode utilizar serviços gerenciados para bases de dados, filas, armazenamento, monitorização e identidade.

No Azure, por exemplo, uma arquitetura .NET pode utilizar:

  • Azure App Service para hospedar aplicações web e APIs.

  • Azure SQL Database para persistência relacional.

  • Azure Blob Storage para armazenamento de ficheiros.

  • Azure Service Bus para comunicação assíncrona.

  • Azure Key Vault para gestão de segredos.

  • Azure Monitor para monitorização e análise operacional.

Esses serviços resolvem problemas diferentes. Não existe uma obrigação de utilizar todos eles.

A escolha deve considerar os requisitos do sistema, as competências da equipa, os custos e as necessidades de operação.

Bases de dados gerenciadas

Uma base de dados gerenciada pode reduzir o esforço associado a tarefas como manutenção da infraestrutura, aplicação de atualizações e configuração de mecanismos de disponibilidade.

No entanto, isso não elimina a responsabilidade da equipa sobre o modelo de dados, o desempenho das consultas, os índices, as permissões e as estratégias de backup e recuperação.

Uma aplicação .NET que utiliza Entity Framework Core, por exemplo, continua a precisar de consultas eficientes e de uma gestão cuidadosa do ciclo de vida do DbContext.

Filas e mensageria

Uma fila pode ajudar a desacoplar operações que não precisam de acontecer dentro da mesma requisição HTTP.

Imagine uma API que recebe um pedido de compra.

Em vez de executar imediatamente todas as tarefas relacionadas com notificações, geração de documentos e integração com sistemas externos, a aplicação pode registar a encomenda e publicar mensagens para processamento posterior.

Isso permite separar o tempo de resposta da API do trabalho secundário.

Mas a mensageria exige decisões adicionais sobre entrega de mensagens, repetição de operações, mensagens duplicadas, ordenação e tratamento de erros.

A infraestrutura gerenciada reduz parte do esforço operacional, mas não substitui o desenho correto dos fluxos de negócio.

Quando evitar serviços gerenciados

Existem situações em que limitações de orçamento, requisitos regulatórios, dependências tecnológicas ou necessidades específicas de controlo justificam outras alternativas.

O importante é compreender o que a equipa ganha e o que continua a precisar de administrar.

A nuvem não elimina a complexidade. Permite escolher onde essa complexidade será tratada.

8. Desenhe a comunicação para tolerar falhas

Em ambientes cloud, falhas temporárias de rede, indisponibilidade de serviços e atrasos nas respostas são situações que precisam de ser consideradas no desenho da aplicação.

Uma chamada HTTP para outro serviço pode demorar mais do que o esperado ou falhar completamente.

Se a aplicação não estiver preparada para isso, uma dependência temporariamente indisponível pode afetar toda a operação.

Por esse motivo, a resiliência deve ser considerada desde o início.

Configure timeouts

Uma operação externa não deve ficar à espera indefinidamente.

Configure limites de tempo adequados ao tipo de operação e aos objetivos de desempenho da aplicação.

Os timeouts devem considerar o tempo total permitido para concluir uma operação, incluindo eventuais tentativas adicionais.

Utilize retries com critério

Uma repetição pode ajudar quando ocorre uma falha transitória.

Contudo, repetir todas as operações indiscriminadamente pode aumentar a carga sobre um serviço que já está sobrecarregado.

Uma política de retry deve considerar o tipo de erro, o número de tentativas, o intervalo entre elas e o tempo total disponível.

A utilização de intervalos progressivos, conhecidos como exponential backoff, pode ajudar a evitar novas tentativas demasiado frequentes.

Também é importante compreender se a operação pode ser repetida em segurança.

Por exemplo, repetir uma consulta de leitura normalmente apresenta menos riscos do que repetir uma operação que cria uma encomenda ou efetua um pagamento.

Considere o circuit breaker

O padrão Circuit Breaker ajuda a impedir que a aplicação continue a enviar pedidos para uma dependência que está a falhar repetidamente.

Em vez de insistir continuamente nas chamadas, o mecanismo interrompe temporariamente as tentativas e permite que o sistema recupere.

Esse padrão é especialmente relevante quando existem várias dependências externas e uma falha pode provocar o esgotamento de recursos na aplicação.

No .NET, as estratégias de resiliência podem ser implementadas com bibliotecas apropriadas e mecanismos integrados no ecossistema atual.

O importante é evitar a criação de políticas duplicadas ou contraditórias em diferentes camadas da aplicação.

A documentação da Microsoft sobre aplicações resilientes apresenta orientações para lidar com falhas parciais e comunicação entre serviços.

Referência: https://learn.microsoft.com/dotnet/architecture/microservices/implement-resilient-applications/

9. Utilize comunicação assíncrona quando fizer sentido

Nem todas as operações precisam de ser concluídas antes de a API devolver uma resposta.

Em muitas aplicações empresariais, tarefas como enviar notificações, processar documentos, atualizar sistemas externos e gerar relatórios podem ser executadas em segundo plano.

Uma arquitetura baseada em mensageria permite separar o serviço que produz uma mensagem do componente responsável por a processar.

Um fluxo simplificado pode ser representado assim:

Cliente
   |
   v
API ASP.NET Core
   |
   v
Registo da encomenda
   |
   v
Publicação de mensagem
   |
   v
Fila ou tópico
   |
   v
Worker .NET
   |
   +----> Envio de notificação
   |
   +----> Integração externa
   |
   +----> Geração de documento

Nesse modelo, a API não precisa de executar todas as tarefas secundárias durante o pedido HTTP.

Um worker pode ser implementado com .NET Worker Service e hospedado num ambiente adequado ao perfil de execução, como um container ou um serviço de computação gerenciado.

A mensageria também ajuda a absorver picos de trabalho, desde que a capacidade dos consumidores e os limites de processamento sejam devidamente configurados.

Não confunda processamento assíncrono com processamento garantidamente único

Os sistemas de mensageria podem entregar mensagens mais de uma vez, dependendo das garantias e da configuração utilizadas.

Por isso, os consumidores devem ser preparados para lidar com duplicações quando o cenário exigir esse comportamento.

Uma estratégia comum é tornar o processamento idempotente.

Isso significa que processar a mesma mensagem repetidamente não deve produzir efeitos de negócio duplicados.

Por exemplo, um consumidor que regista o pagamento de uma encomenda pode utilizar um identificador único da operação e verificar se ela já foi processada antes de aplicar novamente a alteração.

Também é necessário considerar o que acontece quando a gravação na base de dados é concluída, mas a publicação da mensagem falha.

Em cenários que exigem consistência entre persistência e publicação de eventos, o padrão Transactional Outbox pode ajudar a coordenar essas operações.

A implementação correta depende das garantias transacionais disponíveis e do desenho do sistema.

10. Prepare a persistência para escalar

A base de dados é frequentemente um dos componentes mais importantes de uma arquitetura .NET.

Mesmo quando a API consegue escalar horizontalmente, uma base de dados sobrecarregada pode limitar o desempenho de todo o sistema.

Por isso, é importante pensar na persistência desde o início.

Otimize o acesso aos dados

No Entity Framework Core, evite carregar entidades e relacionamentos que não são necessários para a operação.

Utilize projeções para obter apenas os campos relevantes, analise as consultas geradas e verifique se os índices correspondem aos padrões reais de acesso.

Em operações de leitura que não precisam de alterar entidades, AsNoTracking() pode reduzir o trabalho de acompanhamento de alterações.

Exemplo:

var orders = await dbContext.Orders
    .AsNoTracking()
    .Where(order => order.CustomerId == customerId)
    .Select(order => new OrderSummaryDto
    {
        Id = order.Id,
        Total = order.Total,
        Status = order.Status
    })
    .ToListAsync(cancellationToken);

O exemplo seleciona apenas os campos necessários para construir o resultado.

Ainda assim, o desempenho depende do volume de dados, dos índices, do plano de execução e das características da base de dados.

Utilize paginação

Devolver milhares de registos numa única resposta pode aumentar o consumo de memória, a latência e o tráfego de rede.

A paginação permite limitar a quantidade de dados devolvidos por operação.

Para conjuntos de dados grandes ou que mudam frequentemente, a paginação por cursor pode ser mais adequada do que a utilização de offsets elevados.

A estratégia deve ser escolhida considerando a ordenação, a estabilidade dos resultados e o padrão de navegação da aplicação.

Considere o connection pooling

O acesso à base de dados deve utilizar corretamente os mecanismos de reutilização de ligações disponibilizados pelo fornecedor.

No caso do SQL Server, o provider utilizado pelo EF Core normalmente beneficia do connection pooling disponibilizado pelo SqlClient.

É importante evitar abrir ligações desnecessariamente, manter transações abertas durante operações demoradas ou criar uma carga excessiva sobre a base de dados.

Aumentar o número de instâncias da API pode aumentar também a quantidade de ligações concorrentes.

Por isso, a capacidade de escala deve ser analisada de forma conjunta entre aplicação, base de dados e limites de infraestrutura.

Pense nas migrações

As alterações ao esquema da base de dados precisam de fazer parte do processo de deployment.

Aplicar migrações automaticamente no arranque de todas as instâncias pode provocar concorrência, atrasos ou problemas de permissão.

Em sistemas de produção, é frequentemente mais seguro executar as alterações de esquema através de uma etapa controlada do pipeline, com validação, permissões apropriadas e um plano de recuperação.

Quando existem várias versões da aplicação em execução durante um deployment gradual, as alterações devem ser planeadas para manter a compatibilidade entre versões.

Uma estratégia comum é expandir o esquema primeiro, publicar o código compatível com o esquema novo e remover estruturas antigas apenas quando já não forem utilizadas.

11. Trate a segurança como parte da arquitetura

Uma aplicação preparada para a nuvem precisa de proteger os dados, as identidades e os canais de comunicação.

Não basta implementar autenticação na API e considerar o assunto resolvido.

A segurança deve abranger a aplicação, a infraestrutura, as dependências e os processos de deployment.

Utilize autenticação e autorização adequadas

O ASP.NET Core oferece mecanismos para implementar autenticação e autorização.

Em APIs empresariais, a integração com um fornecedor de identidade pode permitir utilizar protocolos e mecanismos padronizados para autenticar utilizadores e aplicações.

É importante distinguir autenticação de autorização.

A autenticação determina quem está a realizar a operação. A autorização determina se essa identidade tem permissão para a executar.

Uma API pode validar corretamente um token e ainda apresentar uma vulnerabilidade se não verificar se o utilizador tem acesso ao recurso solicitado.

Por exemplo, saber o identificador de uma encomenda não deve ser suficiente para consultar os dados de qualquer cliente.

Aplique o princípio do menor privilégio

Cada componente deve receber apenas as permissões necessárias para cumprir a sua função.

Uma API que consulta uma base de dados pode não precisar de permissões para eliminar tabelas ou administrar o servidor.

Da mesma forma, um worker responsável por processar documentos não precisa necessariamente de acesso a todos os recursos da subscrição cloud.

No Azure, identidades gerenciadas e mecanismos de controlo de acesso baseados em funções podem ajudar a reduzir a necessidade de credenciais armazenadas manualmente.

Proteja os dados em trânsito e em repouso

Utilize mecanismos adequados de encriptação nas comunicações e no armazenamento.

Também é necessário avaliar a exposição de dados em logs, mensagens de erro, métricas e rastreamentos.

Informações pessoais, tokens e credenciais não devem ser registados indiscriminadamente.

A política de logging deve permitir diagnosticar problemas sem transformar os sistemas de observabilidade numa fonte de exposição de dados sensíveis.

12. Implemente observabilidade desde o início

Uma aplicação distribuída pode apresentar falhas difíceis de reproduzir localmente.

Uma requisição pode atravessar uma API, uma fila, um worker, uma base de dados e vários serviços externos antes de concluir uma operação.

Sem observabilidade, descobrir onde ocorreu o problema pode exigir a análise manual de diversos registos.

Por isso, logs, métricas e rastreamento distribuído devem fazer parte do desenho arquitetural.

Logs estruturados

Os logs devem permitir compreender o que aconteceu, quando aconteceu e em que contexto.

No .NET, o sistema Microsoft.Extensions.Logging permite produzir registos através de diferentes fornecedores.

Prefira registos estruturados em vez de mensagens que misturam todos os valores num único texto.

Por exemplo:

logger.LogInformation(
    "Encomenda {OrderId} processada para o cliente {CustomerId}",
    orderId,
    customerId);

Os valores ficam associados a campos que podem ser pesquisados e analisados nas ferramentas de monitorização.

Evite incluir dados sensíveis sem uma necessidade justificada.

Métricas

As métricas permitem acompanhar o comportamento do sistema ao longo do tempo.

Alguns indicadores úteis incluem:

  • Número de requisições recebidas.

  • Taxa de erros.

  • Latência das operações.

  • Consumo de memória.

  • Utilização de CPU.

  • Quantidade de mensagens pendentes.

  • Tempo de processamento das filas.

  • Falhas nas integrações externas.

O objetivo não é recolher todos os indicadores possíveis, mas identificar aqueles que ajudam a detetar problemas e a tomar decisões.

Rastreamento distribuído

O tracing permite acompanhar uma operação ao longo dos diferentes componentes do sistema.

Se uma requisição demora vários segundos, o rastreamento pode ajudar a identificar se o tempo foi gasto na API, numa consulta SQL, numa chamada HTTP ou no processamento assíncrono.

O OpenTelemetry disponibiliza mecanismos padronizados para recolher e exportar dados de telemetria.

No ecossistema .NET, pode ser integrado com as APIs de logging, métricas e atividades de rastreamento, permitindo utilizar diferentes plataformas de observabilidade.

No Azure, o Azure Monitor e o Application Insights podem fazer parte dessa estratégia.

Referência oficial: https://learn.microsoft.com/pt-pt/dotnet/core/diagnostics/observability-with-otel

A observabilidade deve ser pensada antes da entrada em produção. Quando a equipa começa a recolher os dados necessários apenas depois de surgir um incidente, pode descobrir que não possui informação suficiente para identificar a causa.

13. Automatize o deployment e a infraestrutura

Uma arquitetura preparada para a nuvem não depende de procedimentos manuais difíceis de reproduzir.

Se a publicação de uma aplicação exigir que alguém se ligue a um servidor, copie ficheiros, altere configurações e execute comandos manualmente, cada deployment terá uma margem adicional de risco.

A automação ajuda a tornar o processo mais previsível.

Um pipeline de CI/CD pode incluir:

  1. Restauro das dependências.

  2. Compilação da solução.

  3. Execução dos testes automatizados.

  4. Análise estática e verificações de segurança.

  5. Criação do artefacto ou da imagem container.

  6. Publicação do artefacto num repositório.

  7. Deployment no ambiente de destino.

  8. Verificação da saúde da aplicação.

A sequência exata depende da tecnologia de hospedagem e das práticas da equipa.

Utilize infraestrutura como código

A infraestrutura como código permite definir recursos cloud através de ficheiros e configurações versionadas.

Em ambientes Azure, tecnologias como Bicep e Terraform podem ser utilizadas para declarar recursos, configurações e dependências.

Isso ajuda a reduzir diferenças entre ambientes e facilita a revisão das alterações de infraestrutura.

Também permite reconstruir ambientes de forma mais previsível, desde que os recursos e os dados necessários sejam tratados adequadamente.

Planeie o rollback

Um deployment não deve ser considerado bem-sucedido apenas porque terminou sem erros.

É importante verificar se a aplicação responde corretamente e se os indicadores operacionais permanecem dentro dos limites esperados.

Quando uma nova versão apresenta problemas, a equipa deve ter uma estratégia para reverter a publicação ou interromper a progressão do deployment.

No entanto, reverter o código não resolve automaticamente alterações incompatíveis na base de dados.

Por isso, a estratégia de rollback precisa considerar conjuntamente a aplicação, o esquema de dados e as mensagens que podem já ter sido processadas.

A automação deve reduzir o risco sem eliminar a capacidade de intervenção quando necessário.

14. Containers, Kubernetes ou serviços gerenciados?

A utilização de containers é comum em arquiteturas cloud-native, mas a escolha do modelo de hospedagem deve acompanhar as necessidades da aplicação.

No ecossistema .NET, existem várias opções.

Azure App Service

O Azure App Service pode ser uma boa escolha para aplicações web e APIs que precisam de hospedagem gerenciada sem exigir a administração direta de uma plataforma de orquestração.

É particularmente interessante quando a prioridade é reduzir o esforço operacional e publicar aplicações ASP.NET Core com relativa simplicidade.

Azure Container Apps

O Azure Container Apps permite executar aplicações baseadas em containers num ambiente gerenciado que suporta cenários de escalabilidade e aplicações distribuídas.

Pode ser uma alternativa para APIs, workers e componentes que precisam de executar em containers sem que a equipa tenha de administrar diretamente um cluster Kubernetes.

A adequação depende dos requisitos de rede, escala, integração e operação.

Azure Kubernetes Service

O Azure Kubernetes Service, ou AKS, oferece uma plataforma gerenciada baseada em Kubernetes.

Pode fazer sentido para organizações que precisam de maior controlo sobre a orquestração de containers, padrões de deployment, rede, políticas operacionais e execução de muitos serviços.

Contudo, Kubernetes introduz conceitos e responsabilidades adicionais.

A equipa precisa de compreender deployment, serviços, ingress, gestão de recursos, segurança, observabilidade, atualizações do cluster e operação da plataforma.

A existência de um serviço gerenciado não elimina essas responsabilidades.

Como escolher?

Uma abordagem prática é começar pela solução que satisfaz os requisitos com menor complexidade operacional.

Se uma API ASP.NET Core pode ser hospedada adequadamente num serviço gerenciado, talvez não exista uma vantagem suficiente para introduzir Kubernetes.

Se a organização já opera uma plataforma Kubernetes, possui competências nessa área e precisa de funcionalidades específicas de orquestração, a decisão pode ser diferente.

A pergunta não deve ser qual tecnologia é mais moderna.

A pergunta deve ser qual opção oferece o equilíbrio mais adequado entre custo, flexibilidade, segurança, escalabilidade e esforço operacional.

15. Considere o .NET Aspire para aplicações distribuídas

À medida que uma solução .NET cresce, executar e diagnosticar os diferentes componentes durante o desenvolvimento pode tornar-se complicado.

Uma API pode depender de uma base de dados, de um cache, de um worker e de um sistema de mensageria.

O .NET Aspire foi desenvolvido para facilitar a construção, a execução e a observabilidade de aplicações distribuídas.

O ecossistema inclui um modelo para descrever recursos e dependências, integrações para componentes comuns e ferramentas para acompanhar o comportamento da aplicação durante o desenvolvimento.

Por exemplo, uma solução pode incluir uma API ASP.NET Core, um worker e uma base de dados. O modelo de aplicação do Aspire permite representar esses componentes e as respetivas dependências, ajudando a organizar a experiência de desenvolvimento.

O Aspire também disponibiliza recursos de telemetria e ferramentas para diagnosticar a aplicação.

É importante, contudo, compreender o seu papel.

O Aspire não substitui uma boa arquitetura de domínio, não elimina a necessidade de segurança e não resolve automaticamente problemas de consistência distribuída.

É uma ferramenta que pode reduzir o atrito no desenvolvimento e na operação de aplicações compostas por vários componentes.

Para projetos .NET que estão a evoluir de uma aplicação simples para um sistema com múltiplas dependências, pode ser uma opção interessante a avaliar.

Documentação oficial: https://learn.microsoft.com/dotnet/aspire/get-started/aspire-overview

16. Um exemplo de arquitetura .NET preparada para a nuvem

Para tornar os conceitos mais concretos, vamos considerar uma aplicação empresarial responsável por gerir clientes e encomendas.

O sistema precisa disponibilizar uma API, armazenar os dados, enviar notificações e integrar-se com um serviço externo de faturação.

Uma arquitetura inicial poderia ser organizada da seguinte forma:

                    Utilizadores
                         |
                         v
                 API ASP.NET Core
                         |
             +-----------+-----------+
             |                       |
             v                       v
       Azure SQL Database       Azure Service Bus
                                     |
                                     v
                              Worker .NET
                                     |
                           +---------+---------+
                           |                   |
                           v                   v
                    Serviço externo    Serviço de notificações

A API recebe as requisições e executa as regras de negócio necessárias para registar as encomendas.

Os dados são armazenados numa base de dados relacional.

As tarefas que não precisam de bloquear a resposta podem ser encaminhadas para processamento assíncrono através do Azure Service Bus.

Um worker .NET consome as mensagens e executa as operações correspondentes.

A aplicação utiliza um mecanismo de identidade apropriado para aceder aos recursos cloud, mantém as configurações fora do código e envia telemetria para uma plataforma de observabilidade.

O deployment é automatizado através de um pipeline que executa testes e publica as alterações.

A arquitetura pode evoluir gradualmente.

Se o volume de processamento aumentar, o worker poderá precisar de mais capacidade. Se a API passar a exigir uma estratégia de escalabilidade diferente, o modelo de hospedagem poderá ser revisto.

Se a integração com a faturação se tornar um componente com requisitos operacionais próprios, poderá ser considerada uma separação adicional.

O importante é que cada decisão responda a uma necessidade identificada.

Não é necessário transformar imediatamente todos os componentes em microservices nem adotar Kubernetes apenas para afirmar que o sistema é cloud-native.

Uma arquitetura preparada para a nuvem é aquela que consegue aproveitar as capacidades da plataforma sem introduzir complexidade desnecessária.

17. Checklist para avaliar uma arquitetura .NET cloud-native

Antes de publicar uma aplicação na nuvem, vale a pena verificar se os principais aspetos arquiteturais foram considerados.

Organização e manutenção

  • As regras de negócio estão separadas dos detalhes de infraestrutura?

  • Os módulos possuem responsabilidades bem definidas?

  • As dependências entre componentes são explícitas?

  • A arquitetura permite testar os casos de uso sem depender de todos os serviços externos?

Escalabilidade

  • A aplicação pode executar em múltiplas instâncias?

  • O estado partilhado está armazenado num local apropriado?

  • As tarefas em segundo plano evitam execuções duplicadas indesejadas?

  • A base de dados suporta a carga esperada?

  • Existem limites de concorrência e consumo de recursos?

Resiliência

  • As chamadas externas possuem timeouts adequados?

  • As tentativas de repetição estão limitadas?

  • As operações repetidas são seguras quando necessário?

  • As falhas das dependências são tratadas de forma controlada?

  • Existe uma estratégia para mensagens que falham repetidamente?

Segurança

  • Os segredos estão fora do código-fonte?

  • A aplicação utiliza identidades e permissões apropriadas?

  • A autorização é aplicada às operações e aos recursos?

  • Os dados sensíveis estão protegidos?

  • Os logs evitam expor informações confidenciais?

Observabilidade

  • Os logs são estruturados?

  • Existem métricas para acompanhar erros e desempenho?

  • É possível rastrear uma operação entre diferentes componentes?

  • Existem alertas para os problemas que exigem intervenção?

  • A equipa consegue diagnosticar falhas em produção?

Deployment

  • O build e os testes são executados automaticamente?

  • A infraestrutura está versionada?

  • As alterações à base de dados são controladas?

  • Existe uma estratégia de rollback?

  • A saúde da aplicação é verificada após a publicação?

Essa checklist não substitui uma avaliação específica do sistema, mas ajuda a identificar lacunas que frequentemente passam despercebidas durante o desenvolvimento.

Conclusão

Pensar numa arquitetura .NET preparada para a nuvem significa projetar um sistema que consiga operar num ambiente dinâmico, lidar com falhas, escalar quando necessário e evoluir sem exigir mudanças desproporcionais a cada novo requisito.

Isso começa com decisões fundamentais: compreender os requisitos do negócio, estabelecer limites claros entre responsabilidades, externalizar configurações, proteger segredos e reduzir dependências desnecessárias da infraestrutura local.

Depois, é necessário pensar na forma como a aplicação se comportará em produção.

A execução de múltiplas instâncias, a comunicação assíncrona, a resiliência, a observabilidade e a automação do deployment são aspetos que influenciam diretamente a capacidade de operar o sistema com segurança e previsibilidade.

Também é importante evitar a complexidade prematura.

Nem toda aplicação precisa de microservices. Nem toda API precisa de Kubernetes. E nem todo componente precisa de ser transformado num serviço independente.

Um monólito modular, construído com ASP.NET Core, organizado de forma consistente e publicado através de um processo automatizado, pode ser uma excelente base para uma arquitetura cloud-native.

O caminho mais sustentável consiste em começar com uma arquitetura simples, estabelecer bons limites internos e evoluir conforme surgem necessidades reais de escala, autonomia ou disponibilidade.

No final, a melhor arquitetura .NET preparada para a nuvem não é necessariamente a que utiliza mais tecnologias. É aquela que resolve os problemas do negócio, mantém os custos sob controlo e permite que a equipa continue a desenvolver e operar o sistema com confiança.

E, na tua experiência, qual é o maior desafio ao preparar aplicações .NET para a nuvem: escolher a arquitetura, garantir a escalabilidade, implementar observabilidade ou controlar os custos de infraestrutura?