Compartilhar via


Padrão de Aplicativo Web Moderno para .NET

Serviço de aplicativo do Azure
Porta da frente do Azure
Redis Gerenciado pelo Azure
.NET

Este artigo mostra como implementar o padrão de Aplicativo Web Moderno. O padrão de Aplicativo Web Moderno define como você deve modernizar aplicativos Web na nuvem e introduzir uma arquitetura voltada para serviços. O padrão de Aplicativo Web Moderno fornece diretrizes prescritivas de arquitetura, código e configuração que se alinham aos princípios do Azure Well-Architected Framework e se baseiam no padrão de Aplicativo Web Confiável.

Por que usar o padrão de Aplicativo Web Moderno?

O padrão de Aplicativo Web Moderno pode ajudá-lo a otimizar áreas de alta demanda de um aplicativo Web. Ele fornece diretrizes detalhadas para desassociar essas áreas, o que permite dimensionamento independente para otimização de custos. Essa abordagem permite que você aloque recursos dedicados a componentes críticos, o que melhora o desempenho geral. A divisão de serviços separáveis pode melhorar a confiabilidade, evitando que a lentidão em uma parte do aplicativo afete outras. A desacoplamento também permite que você controle a versão de componentes de aplicativo individuais de forma independente.

Como implementar o padrão de Aplicativo Web Moderno

Este artigo contém diretrizes de arquitetura, código e configuração para implementar o padrão de Aplicativo Web Moderno. Use os links a seguir para acessar as diretrizes necessárias:

  • Diretrizes de arquitetura. Saiba como modularizar componentes de aplicativo Web e selecionar soluções de PaaS (plataforma como serviço) apropriadas.
  • Diretrizes de código. Implemente quatro padrões de design para otimizar os componentes desacoplados: Strangler Fig, Queue-Based Load Leveling, Competing Consumers e Health Endpoint Monitoring.
  • Diretrizes de configuração. Configure a autenticação, a autorização, o dimensionamento automático e a contêinerização para os componentes desacoplados.

Dica

Logotipo do GitHub Há uma implementação de referência (aplicativo de amostra) do padrão de Aplicativo Web Moderno. Ela representa o estado final da implementação do Aplicativo Web Moderno. Trata-se de um aplicativo Web de nível de produção que apresenta todas as atualizações de código, arquitetura e configuração discutidas neste artigo. Implante e use a implementação de referência para orientar sua implementação do padrão de Aplicativo Web Modern.

Diretrizes para arquitetura

O padrão de Aplicativo Web Moderno baseia-se no padrão de Aplicativo Web Confiável. Requer alguns componentes arquitetônicos extras para implementar. Você precisa de uma fila de mensagens, uma plataforma de contêiner, um repositório de dados de serviço desacoplado e um registro de contêiner. O diagrama a seguir ilustra a arquitetura da linha de base.

Diagrama mostrando a arquitetura de linha de base do padrão de Aplicativo Web Moderno.

Para um SLO (objetivo de nível de serviço) mais alto, é possível adicionar uma segunda região à arquitetura do aplicativo Web. Se você adicionar uma segunda região, precisará configurar o balanceador de carga para rotear o tráfego para essa região para dar suporte a uma configuração ativa-ativa ou ativa-passiva. Use uma topologia de rede hub-and-spoke para centralizar e compartilhar recursos, como um firewall de rede. Acesse o repositório de contêiner por meio da rede virtual do hub. Se você tiver máquinas virtuais, adicione um host bastion à rede virtual do hub para gerenciá-las com segurança aprimorada. O diagrama a seguir ilustra essa arquitetura.

Diagrama mostrando a arquitetura de padrão de aplicativo Web moderno com uma segunda região e topologia de rede hub-and-spoke.

Desacoplar a arquitetura

Para implementar o padrão de Aplicativo Web Moderno, você precisa dividir a arquitetura de aplicativo Web existente. A desassociação da arquitetura envolve dividir um aplicativo monolítico em serviços independentes menores que são responsáveis por um recurso ou funcionalidade específico. Esse processo envolve a avaliação do aplicativo Web atual, modificação da arquitetura e, por fim, extração do código do aplicativo Web para uma plataforma de contêiner. O objetivo é identificar e extrair sistematicamente os serviços de aplicativos que mais se beneficiam com o desacoplamento. Para dividir sua arquitetura, siga estas recomendações:

  • Identifique os limites de serviço. Aplique princípios de design controlados por domínio para identificar contextos limitados em seu aplicativo monolítico. Cada contexto limitado representa um limite lógico e pode ser um candidato para um serviço separado. Os serviços que representam funções de negócios distintas e têm menos dependências são bons candidatos para a divisão.

  • Avalie os benefícios do serviço. Concentre-se nos serviços que mais se beneficiam do dimensionamento independente. Desassociar esses serviços e converter tarefas de processamento de operações síncronas para assíncronas permite um gerenciamento de recursos mais eficiente, dá suporte a implantações independentes e reduz o risco de afetar outras partes do aplicativo durante atualizações ou alterações. Por exemplo, é possível separar o check-out da ordem do processamento da ordem.

  • Avalie a viabilidade técnica. Examine a arquitetura atual para identificar restrições técnicas e dependências que possam afetar o processo de divisão. Planeje a forma como os dados são gerenciados e compartilhados entre os serviços. Os serviços divididos devem gerenciar seus próprios dados e minimizar o acesso direto ao banco de dados entre os limites do serviço.

  • Implante os serviços do Azure. Selecione e implante os serviços do Azure necessários para dar suporte ao serviço de aplicativo Web que você pretende extrair. Para obter diretrizes, consulte Selecionar os serviços corretos do Azure.

  • Desacoplar serviços de aplicativos Web. Defina interfaces e APIs claras para permitir que os serviços de aplicativo Web recém-extraídos interajam com outras partes do sistema. Projete uma estratégia de gerenciamento de dados que permita que cada serviço gerencie seus próprios dados, garantindo consistência e integridade. Para obter estratégias de implementação específicas e padrões de design a serem usados durante esse processo de extração, consulte a seção de diretrizes de código deste artigo.

  • Use o armazenamento independente para serviços divididos. Cada serviço desacoplado deve ter seu próprio armazenamento de dados isolado para facilitar o controle de versão, a implantação e a escalabilidade independentes e manter a integridade dos dados. Por exemplo, a implementação de referência separa o serviço de renderização de tíquetes da API Web e elimina a necessidade de o serviço acessar o banco de dados da API. Em vez disso, o serviço passa a URL em que as imagens de tíquete foram geradas de volta para a API Web por meio de uma mensagem do Barramento de Serviço do Azure e a API mantém o caminho para seu banco de dados.

  • Implemente pipelines de implantação separados para cada serviço dividido. Pipelines de implantação separados permitem que cada serviço seja atualizado no seu próprio ritmo. Caso equipes ou organizações diferentes na sua empresa possuam serviços diferentes, o uso de pipelines de implantação separados dará a cada equipe controle sobre suas próprias implantações. Use ferramentas de CI/CD (integração contínua e entrega contínua), como Jenkins, GitHub Actions ou Azure Pipelines, para configurar esses pipelines.

  • Revise os controles de segurança. Certifique-se de que seus controles de segurança estejam atualizados para acomodar a nova arquitetura, incluindo regras de firewall e controles de acesso.

Selecione os serviços certos do Azure

Para cada serviço do Azure na sua arquitetura, consulte o Guia de serviço do Azure relevante no Well-Architected Framework. Para o padrão de Aplicativo Web Moderno, é necessário um sistema de mensagens para dar suporte a mensagens assíncronas, uma plataforma de aplicativo que dê suporte à conteinerização e um repositório de imagens de contêiner.

  • Escolha uma fila de mensagens. Uma fila de mensagens é um componente importante das arquiteturas orientadas ao serviço. Ele separa remetentes e destinatários de mensagens para habilitar mensagens assíncronas. Use as orientações sobre como escolher um serviço de mensagens do Azure para escolher um sistema de mensagens do Azure que dê suporte às suas necessidades de design. O Azure tem três serviços de mensagens: Grade de Eventos do Azure, Hubs de Eventos do Azure e Barramento de Serviço do Azure. Comece com o Barramento de Serviço como a opção padrão e use as outras duas opções se o Barramento de Serviço não atender às suas necessidades.

    Serviço Caso de uso
    Barramento de Serviço Escolha o Barramento de Serviço para entrega confiável, ordenada e possivelmente transacional de mensagens de alto valor em aplicativos empresariais.
    Grade de Eventos Escolha a Grade de Eventos quando precisar lidar com um grande número de eventos discretos com eficiência. A Grade de Eventos é escalonável para aplicativos controlados por eventos em que muitos pequenos eventos independentes (como alterações de estado de recurso) precisam ser roteados para assinantes em um modelo de publicação-assinatura de baixa latência.
    Hubs de Eventos Escolha Hubs de Eventos para ingestão de dados de alta taxa de transferência, como telemetria, logs ou análise em tempo real. Os Hubs de Eventos são otimizados para cenários de streaming em que os dados em massa precisam ser ingeridos e processados continuamente.
  • Implemente um serviço de contêiner. Para os componentes do seu aplicativo que você deseja colocar em contêiner, você precisa de uma plataforma de aplicativo que dê suporte a contêineres. As diretrizes escolher um serviço de contêiner do Azure podem ajudá-lo a tomar sua decisão. O Azure tem três serviços de contêiner principais: Aplicativos de Contêiner do Azure, AKS (Serviço de Kubernetes do Azure) e Serviço de Aplicativo do Azure. Comece com os Aplicativos de Contêiner como a opção padrão e use as outras duas opções se os Aplicativos de Contêiner não atenderem às suas necessidades.

    Serviço Caso de uso
    Aplicativos de Contêiner Escolha Aplicativos de Contêiner se você precisar de uma plataforma sem servidor que dimensione e gerencie automaticamente contêineres em aplicativos controlados por eventos.
    AKS Escolha o AKS se precisar de controle detalhado sobre as configurações do Kubernetes e recursos avançados para dimensionamento, rede e segurança.
    Aplicativos Web para contêineres Escolha Aplicativo Web para Contêineres no Serviço de Aplicativo para a experiência paaS mais simples.
  • Implemente um repositório de contêiner. Quando você usa qualquer serviço de computação baseado em contêiner, precisa ter um repositório para armazenar as imagens de contêiner. Você pode usar um registro de contêiner público, como o Docker Hub, ou um registro gerenciado, como o Registro de Contêiner do Azure. A introdução aos registros de contêiner nas diretrizes do Azure pode ajudá-lo a tomar sua decisão.

Orientações de código

Para dividir e extrair com êxito um serviço independente, é necessário atualizar o código do aplicativo Web com os seguintes padrões de design: o padrão Estrangulador Fig, o padrão Nivelamento de Carga Baseado em Fila, o padrão Consumidores Concorrentes, o padrão Monitoramento de Ponto de Extremidade de Integridade e o padrão Repetição. As funções desses padrões são ilustradas aqui:

Diagrama mostrando as funções dos padrões de design na arquitetura de padrão do Aplicativo Web Moderno.

  1. Padrão Estrangulador Fig: o padrão Estrangulador Fig migra de forma incremental a funcionalidade de um aplicativo monolítico para o serviço dividido. Implemente esse padrão no aplicativo Web principal para migrar gradualmente a funcionalidade para serviços independentes, direcionando o tráfego com base em pontos de extremidade.

  2. Padrão de Nivelamento de Carga Baseado em Fila: o padrão de Nivelamento de Carga Baseado em Fila gerencia o fluxo de mensagens entre o produtor e o consumidor usando uma fila como buffer. Implemente esse padrão na base de código que produz mensagens para a fila. Em seguida, o serviço desacoplado consome essas mensagens da fila de forma assíncrona.

  3. Padrão Consumidores Concorrentes: o padrão Consumidores Concorrentes permite que várias instâncias do serviço dividido leiam de maneira independente da mesma fila de mensagens e concorram para processar mensagens. Implemente esse padrão no serviço dividido para distribuir tarefas em várias instâncias.

  4. Padrão Monitoramento de Ponto de Extremidade de Integridade: o padrão de Monitoramento de Ponto de Extremidade de Integridade expõe pontos de extremidade para monitorar o status e a integridade de diferentes partes do aplicativo Web. (4a) Implemente esse padrão no aplicativo Web principal. (4b) Implemente-o também no serviço dividido para rastrear a integridade dos pontos de extremidade.

  5. Padrão Repetição: o padrão Repetição lida com falhas transitórias repetindo operações que podem falhar intermitentemente. (5a) Implemente esse padrão em todas as chamadas de saída para outros serviços do Azure no aplicativo Web principal, como chamadas para fila de mensagens e pontos de extremidade privados. (5b) Implemente também esse padrão no serviço dividido para lidar com falhas transitórias em chamadas para os pontos de extremidade privados.

Cada padrão de design oferece benefícios que se alinham a um ou mais pilares do Well-Architected Framework. Veja a tabela a seguir para obter detalhes.

Padrão de design Local de implementação Confiabilidade (RE) Segurança (SE) Otimização de custo (CO) Excelência operacional (OE) Eficiência de performance (PE) Suporte aos princípios do Well-Architected Framework
Padrão de Estrangulador Fig Aplicativo Web principal RE:08
CO: 07
CO: 08
OE:06
OE: 11
Padrão de nivelamento de carga baseado em fila Aplicativo Web principal (produtor de mensagens) RE:07
RE:07
CO: 12
PE:05
Padrão de Consumidores Concorrentes Serviço dividido RE:05
RE:07
CO:05
CO: 07
PE:05
PE:07
Padrão de monitoramento de ponto de extremidade de integridade Aplicativo Web principal e serviço desacoplado RE:07
RE:10
OE:07
PE:05
Padrão de repetição Aplicativo Web principal e serviço desacoplado RE:07

Implementar o padrão Estrangualdor Fig

Use o padrão do Strangler Fig para migrar gradualmente a funcionalidade da base de código monolítica para novos serviços independentes. Extraia novos serviços da base de código monolítico existente e modernize lentamente partes críticas do aplicativo Web. Para implementar o padrão Estrangulador Fig, siga estas recomendações:

  • Configure uma camada de roteamento. Na base de código do aplicativo Web monolítico, implemente uma camada de roteamento que direcione o tráfego com base em pontos de extremidade. Use a lógica de roteamento personalizada conforme necessário para lidar com regras de negócios específicas para direcionar o tráfego. Por exemplo, se você tiver um ponto de extremidade /users em seu aplicativo monolítico e mover essa funcionalidade para o serviço desacoplado, a camada de roteamento direcionará todas as solicitações para /users para o novo serviço.

  • Gerenciar a distribuição de recursos. Use bibliotecas de gerenciamento de recursos do .NET para implementar sinalizadores de recursos e distribuição em etapas para distribuir gradualmente os serviços desacoplados. O roteamento de aplicativo monolítico existente deve controlar quantas solicitações os serviços divididos recebem. Comece com uma pequena porcentagem de solicitações e aumente o uso ao longo do tempo à medida que você ganha confiança na estabilidade e no desempenho do novo serviço. Por exemplo, a implementação de referência extrai a funcionalidade de renderização de tíquetes em um serviço autônomo, que pode ser introduzido gradualmente para lidar com uma parte maior das solicitações de renderização de tíquetes. À medida que o novo serviço comprova sua confiabilidade e desempenho, ele pode eventualmente assumir toda a funcionalidade de renderização de tíquetes do monolito, concluindo a transição.

  • Use um serviço de fachada (se necessário). Um serviço de fachada é útil quando uma única solicitação precisa interagir com vários serviços ou quando você deseja ocultar a complexidade do sistema subjacente do cliente. No entanto, se o serviço desacoplado não tiver nenhuma API voltada para o público, um serviço de fachada poderá não ser necessário. Na base de código do aplicativo Web monolítico, implemente um serviço de fachada para rotear solicitações para o back-end apropriado (monólito ou microsserviço). No novo serviço desacoplado, verifique se o novo serviço pode lidar com solicitações de forma independente quando acessado por meio da fachada.

Implementar o Padrão de nivelamento de carga baseado em fila

Implemente o padrão de Nivelamento de Carga Baseado em Fila na parte do produtor do serviço desacoplado para lidar de forma assíncrona com tarefas que não precisam de respostas imediatas. Esse padrão aprimora a capacidade de resposta e a escalabilidade geral do sistema usando uma fila para gerenciar a distribuição da carga de trabalho. Ele permite que o serviço dividido processe solicitações a uma taxa consistente. Para implementar esse padrão de forma eficaz, siga estas recomendações:

  • Use o serviço de enfileiramento de mensagens sem bloqueio. Verifique se o processo que envia mensagens para a fila não bloqueia outros processos enquanto aguarda que o serviço dividido lide com mensagens na fila. Se o processo exigir o resultado da operação de serviço dividido, tenha uma maneira alternativa de lidar com a situação enquanto aguarda a conclusão da operação na fila. Por exemplo, a implementação de referência usa o Barramento de Serviço e a await palavra-chave with messageSender.PublishAsync() para publicar mensagens de forma assíncrona na fila sem bloquear o thread que executa esse código:

    // Asynchronously publish a message without blocking the calling thread.
    await messageSender.PublishAsync(new TicketRenderRequestMessage(Guid.NewGuid(), ticket, null, DateTime.Now), CancellationToken.None);
    

    Essa abordagem garante que o aplicativo principal permaneça responsivo e consiga lidar com outras tarefas simultaneamente, enquanto o serviço dividido processa as solicitações enfileiradas a uma taxa gerenciável.

  • Implemente a repetição e a remoção de mensagens. Implemente um mecanismo para repetir o processamento de mensagens enfileiradas que não podem ser processadas com êxito. Se as falhas persistirem, essas mensagens deverão ser removidas da fila. Por exemplo, o Barramento de Serviço tem recursos internos de repetição e fila de devoluções.

  • Configure processamento de mensagens idempotentes. A lógica que processa mensagens da fila deve ser idempotente para lidar com casos em que uma mensagem pode ser processada mais de uma vez. Por exemplo, a implementação de referência usa ServiceBusClient.CreateProcessor e AutoCompleteMessages = trueReceiveMode = ServiceBusReceiveMode.PeekLock para garantir que as mensagens sejam processadas apenas uma vez e possam ser reprocessadas em caso de falha. O código a seguir ilustra essa lógica.

    // Create a processor for idempotent message processing.
    var processor = serviceBusClient.CreateProcessor(path, new ServiceBusProcessorOptions
    {
        // Allow the messages to be auto-completed
        // if processing finishes without failure.
        AutoCompleteMessages = true,
    
        // PeekLock mode provides reliability in that unsettled messages
        // will be redelivered on failure.
        ReceiveMode = ServiceBusReceiveMode.PeekLock,
    
        // Containerized processors can scale at the container level
        // and need not scale via the processor options.
        MaxConcurrentCalls = 1,
        PrefetchCount = 0
    });
    
  • Gerencie as alterações na experiência. O processamento assíncrono pode fazer com que as tarefas não sejam concluídas imediatamente. Os usuários devem estar cientes quando sua tarefa ainda estiver sendo processada para definir as expectativas corretas e evitar confusão. Use sinalizações visuais ou mensagens para indicar que uma tarefa está em andamento. Dê aos usuários a opção de receber notificações quando sua tarefa for concluída, como um email ou notificação por push.

Implementar o padrão Consumidores Concorrentes

Implemente o padrão Consumidores Concorrentes nos serviços divididos para gerenciar tarefas de entrada da fila de mensagens. Esse padrão envolve a distribuição de tarefas em várias instâncias de serviços divididos. Esses serviços processam mensagens da fila, aprimorando o balanceamento de carga e aumentando a capacidade do sistema de lidar com solicitações simultâneas. O padrão Consumidores Concorrentes é eficaz quando:

  • A sequência de processamento de mensagens não é essencial.
  • A fila permanece não afetada por mensagens malformadas.
  • A operação de processamento é idempotente, o que significa que ela pode ser aplicada várias vezes sem alterar o resultado além da aplicação inicial.

Para implementar o padrão Consumidores Concorrentes, siga estas recomendações:

  • Lide com mensagens simultâneas. Quando o sistema recebe mensagens de uma fila, verifique se o sistema foi projetado para lidar com várias mensagens simultaneamente. Defina o máximo de chamadas simultâneas como 1 para que um consumidor separado lide com cada mensagem.

  • Desative a pré-busca. Desabilite a pré-busca de mensagens para que os consumidores busquem mensagens somente quando estiverem prontos.

  • Use modos confiáveis de processamento de mensagens. Use um modo de processamento confiável, como PeekLock (ou equivalente), que repete automaticamente as mensagens que falham no processamento. Esse modo fornece mais confiabilidade do que os métodos de exclusão primeiro. Se um trabalhador não conseguir lidar com uma mensagem, outro deverá ser capaz de processá-la sem erros, mesmo que a mensagem seja processada várias vezes.

  • Implemente tratamento de erros. Encaminhe mensagens malformadas ou não processáveis para uma fila de mensagens mortas separada. Esse design evita o processamento repetitivo. Por exemplo, você pode capturar exceções durante o processamento de mensagens e mover a mensagem problemática para a fila separada.

  • Lide com mensagens fora de ordem. Projete os consumidores para que processem mensagens que chegam fora de sequência. Se você tiver vários consumidores paralelos, eles poderão processar mensagens fora de ordem.

  • Dimensione com base no comprimento da fila. Os serviços que consomem mensagens de uma fila devem considerar o dimensionamento automático com base no comprimento da fila ou usando critérios de dimensionamento adicionais para melhor processar picos de mensagens de entrada.

  • Use uma fila de resposta de mensagem. Se o sistema exigir notificações para processamento pós-mensagem, configure uma resposta dedicada ou uma fila de respostas. Essa configuração separa as mensagens operacionais dos processos de notificação.

  • Use serviços sem estado. Considere o uso de serviços sem estado para processar solicitações de uma fila. Esses serviços permitem o dimensionamento fácil e o uso eficiente de recursos.

  • Configure o registro em log. Integre o registro em log e o tratamento de exceções específicas no fluxo de trabalho de processamento de mensagens. Concentre-se em capturar erros de serialização e direcionar essas mensagens problemáticas para um mecanismo de mensagens mortas. Esses logs fornecem informações valiosas para a solução de problemas.

Por exemplo, a implementação de referência usa o padrão Consumidores Concorrentes em um serviço sem estado em execução em Aplicativos de Contêiner para processar solicitações de renderização de tíquete de uma fila do Barramento de Serviço. Ele configura um processador de fila com:

  • AutoCompleteMessages. Conclui automaticamente as mensagens se elas forem processadas sem falha.
  • ReceiveMode. Usa o modo PeekLock e resgata mensagens se elas não estiverem resolvidas.
  • MaxConcurrentCalls. Defina como 1 para lidar com uma mensagem de cada vez.
  • PrefetchCount. Defina como 0 para evitar mensagens de pré-busca.

O processador registra detalhes de processamento de mensagens, que podem ajudar na solução de problemas e monitoramento. Ele captura erros de desserialização e roteia mensagens inválidas para uma fila de mensagens mortas para evitar o processamento repetitivo de mensagens com falha. O serviço é dimensionado no nível do contêiner, o que permite o tratamento eficiente de picos de mensagens com base no comprimento da fila.

// Create a processor for the given queue that will process
// incoming messages.
var processor = serviceBusClient.CreateProcessor(path, new ServiceBusProcessorOptions
{
    // Allow the messages to be auto-completed
    // if processing finishes without failure.
    AutoCompleteMessages = true,
    // PeekLock mode provides reliability in that unsettled messages
    // are redelivered on failure.
    ReceiveMode = ServiceBusReceiveMode.PeekLock,
    // Containerized processors can scale at the container level
    // and need not scale via the processor options.
    MaxConcurrentCalls = 1,
    PrefetchCount = 0
});

// Called for each message received by the processor.
processor.ProcessMessageAsync += async args =>
{
    logger.LogInformation("Processing message {MessageId} from {ServiceBusNamespace}/{Path}", args.Message.MessageId, args.FullyQualifiedNamespace, args.EntityPath);
    // Unhandled exceptions in the handler will be caught by
    // the processor and result in abandoning and dead-lettering the message.
    try
    {
        var message = args.Message.Body.ToObjectFromJson<T>();
        await messageHandler(message, args.CancellationToken);
        logger.LogInformation("Successfully processed message {MessageId} from {ServiceBusNamespace}/{Path}",args.Message.MessageId, args.FullyQualifiedNamespace, args.EntityPath);
    }
    catch (JsonException)
    {
        logger.LogError("Invalid message body; could not be deserialized to {Type}", typeof(T));
        await args.DeadLetterMessageAsync(args.Message, $"Invalid message body; could not be deserialized to {typeof(T)}",cancellationToken: args.CancellationToken);
    }
};

Implementar o padrão Monitoramento do Ponto de Extremidade de Integridade

Implemente o padrão Monitoramento de Ponto de Extremidade de Integridade no código do aplicativo principal e no código de serviço dividido para acompanhar a integridade dos pontos de extremidade do aplicativo. Orquestradores como AKS ou Aplicativos de Contêiner podem sondar esses pontos de extremidade para verificar a integridade do serviço e reiniciar instâncias não íntegras. Aplicativos ASP.NET Core podem adicionar middleware de verificação de integridade dedicado para atender com eficiência aos dados de integridade do ponto de extremidade e às principais dependências. Para implementar o padrão Monitoramento de Ponto de Extremidade de Integridade, siga estas recomendações:

  • Implemente verificações de integridade. Use middleware de verificações de integridade ASP.NET Core para fornecer pontos de extremidade de verificação de integridade.

  • Valide dependências. Certifique-se de que suas verificações de integridade validem a disponibilidade de dependências de chave, como banco de dados, armazenamento e sistema de mensagens. O pacote não Microsoft AspNetCore.Diagnostics.HealthChecks pode implementar verificações de dependência de verificação de integridade para muitas dependências comuns do aplicativo.

    Por exemplo, a implementação de referência usa o middleware de verificação de integridade do ASP.NET Core para expor pontos de extremidade de verificação de integridade. Ele usa o AddHealthChecks() método no builder.Services objeto. O código valida a disponibilidade de dependências de chave, o Armazenamento de Blobs do Azure e a fila do Barramento de Serviço usando o e AddAzureBlobStorage() os AddAzureServiceBusQueue() métodos, que fazem parte do AspNetCore.Diagnostics.HealthChecks pacote. Os Aplicativos de Contêiner permitem a configuração de investigações de integridade que são monitoradas para avaliar se os aplicativos estão íntegros ou precisam ser reciclados.

    // Add health checks, including health checks for Azure services
    // that are used by this service.
    // The Blob Storage and Service Bus health checks are provided by
    // AspNetCore.Diagnostics.HealthChecks
    // (a popular open source project) rather than by Microsoft. 
    // https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks
    builder.Services.AddHealthChecks()
    .AddAzureBlobStorage(options =>
    {
        // AddAzureBlobStorage will use the BlobServiceClient registered in DI.
        // We just need to specify the container name.
        options.ContainerName = builder.Configuration.GetRequiredConfigurationValue("App:StorageAccount:Container");
    })
    .AddAzureServiceBusQueue(
        builder.Configuration.GetRequiredConfigurationValue("App:ServiceBus:Host"),
        builder.Configuration.GetRequiredConfigurationValue("App:ServiceBus:RenderRequestQueueName"),
        azureCredentials);
    
    // Further app configuration omitted for brevity.
    app.MapHealthChecks("/health");
    
  • Configure implantes do Azure. Configure os recursos do Azure para usar as URLs de verificação de integridade do aplicativo para confirmar a atividade e a preparação. Por exemplo, a implementação de referência usa o Bicep para configurar as URLs de verificação de integridade para confirmar a atividade e a prontidão do implante do Azure. Uma investigação de dinâmica atinge o ponto de extremidade a /health cada 10 segundos após um atraso inicial de 2 segundos.

    probes: [
      {
        type: 'liveness'
        httpGet: {
          path: '/health'
          port: 8080
        }
        initialDelaySeconds: 2
        periodSeconds: 10
      }
    ]
    

Implementar o padrão Repetição

O padrão Repetição permite que os aplicativos se recuperem de falhas transitórias. O padrão Repetição é fundamental para o padrão de Aplicativo Web Confiável, portanto, seu aplicativo Web já deve estar usando o padrão Repetição. Aplique o padrão de repetição às solicitações aos sistemas de mensagens e às solicitações emitidas pelos serviços desacoplados que você extrai do aplicativo Web. Para implementar o padrão Repetição, siga estas recomendações:

  • Configure opções de repetição. Ao integrar com uma fila de mensagens, certifique-se de configurar o cliente responsável pelas interações com a fila com as configurações de repetição apropriadas. Especifique parâmetros como o número máximo de repetições, atraso entre repetições e atraso máximo.

  • Use retirada exponencial. Implemente uma estratégia de retirada exponencial para tentativas de repetição. Essa estratégia envolve aumentar o tempo entre cada repetição exponencialmente, o que ajuda a reduzir a carga no sistema durante períodos de altas taxas de falha.

  • Use a funcionalidade de repetição do SDK. Para serviços que têm SDKs especializados, como Barramento de Serviço ou Armazenamento de Blobs, use os mecanismos de repetição internos. Os mecanismos de repetição internos são otimizados para os casos de uso típicos do serviço e podem lidar com novas tentativas de forma mais eficaz com menos configuração necessária. Por exemplo, a implementação de referência usa a funcionalidade interna de repetição do SDK do Barramento de Serviço (ServiceBusClient e ServiceBusRetryOptions). O ServiceBusRetryOptions objeto busca as configurações para MessageBusOptions definir configurações de repetição, como MaxRetries, Delaye MaxDelayTryTimeout.

    // ServiceBusClient is thread-safe and can be reused for the lifetime
    // of the application.
    services.AddSingleton(sp =>
    {
        var options = sp.GetRequiredService<IOptions<MessageBusOptions>>().Value;
        var clientOptions = new ServiceBusClientOptions
        {
            RetryOptions = new ServiceBusRetryOptions
            {
                Mode = ServiceBusRetryMode.Exponential,
                MaxRetries = options.MaxRetries,
                Delay = TimeSpan.FromSeconds(options.BaseDelaySecondsBetweenRetries),
                MaxDelay = TimeSpan.FromSeconds(options.MaxDelaySeconds),
                TryTimeout = TimeSpan.FromSeconds(options.TryTimeoutSeconds)
            }
        };
        return new ServiceBusClient(options.Host, azureCredential ?? new DefaultAzureCredential(), clientOptions);
    });
    
  • Adote bibliotecas de resiliência padrão para clientes HTTP. Para comunicações HTTP, integre uma biblioteca de resiliência padrão, como Polly ou Microsoft.Extensions.Http.Resilience. Essas bibliotecas oferecem mecanismos abrangentes de repetição que são fundamentais para gerenciar comunicações com serviços Web externos.

  • Trate o bloqueio de mensagens. Para sistemas baseados em mensagens, implemente estratégias de tratamento de mensagens que dão suporte a novas tentativas sem perda de dados, como usar modos de "peek-lock" quando disponível. Certifique-se de que as mensagens com falha sejam repetidas de forma eficaz e movidas para uma fila de mensagens mortas após repetidas falhas.

Implementar rastreamento distribuído

À medida que os aplicativos se tornam mais orientados a serviços e seus componentes são divididos, o monitoramento do fluxo de execução entre os serviços é crucial. O padrão de Aplicativo Web Moderno usa o Application Insights e o Azure Monitor para visibilidade da integridade e do desempenho do aplicativo por meio de APIs OpenTelemetry, que dão suporte ao rastreamento distribuído.

O rastreamento distribuído rastreia uma solicitação do usuário à medida que ela percorre vários serviços. Quando uma solicitação é recebida, ela é marcada com um identificador de rastreamento, que é passado para outros componentes por meio de cabeçalhos HTTP e propriedades do Barramento de Serviço durante a invocação de dependências. Os rastreamentos e os logs incluem o identificador de rastreamento e um identificador de atividade (ou identificador de intervalo), que corresponde ao componente específico e sua atividade pai. Ferramentas de monitoramento como o Application Insights usam essas informações para exibir uma árvore de atividades e logs em diferentes serviços, o que é crucial para monitorar aplicativos distribuídos.

  • Instale bibliotecas OpenTelemetry. Use bibliotecas de instrumentação para habilitar o rastreamento e as métricas de componentes comuns. Adicione instrumentação personalizada com System.Diagnostics.ActivitySource e, System.Diagnostics.Activity se necessário. Use bibliotecas de exportação para escutar diagnósticos do OpenTelemetry e registrá-los em repositórios persistentes. Use exportadores existentes ou crie seus próprios usando System.Diagnostics.ActivityListener.

  • Configure o OpenTelemetry. Use a distribuição do Azure Monitor do OpenTelemetry (Azure.Monitor.OpenTelemetry.AspNetCore). Verifique se ele exporta diagnóstico para o Application Insights e inclui instrumentação interna para métricas comuns, rastreamentos, logs e exceções do runtime do .NET e do ASP.NET Core. Inclua outros pacotes de instrumentação do OpenTelemetry para clientes SQL, Redis e SDK do Azure.

  • Monitore e analise. Depois de configurar o rastreamento, verifique se os logs, rastreamentos, métricas e exceções são capturados e enviados para o Application Insights. Verifique se os identificadores de atividade pai, atividade e rastreamento estão incluídos. Esses identificadores permitem que o Application Insights forneça visibilidade de rastreamento de ponta a ponta entre os limites do HTTP e do Barramento de Serviço. Use essa configuração para monitorar e analisar as atividades do aplicativo em todos os serviços.

O exemplo do Aplicativo Web Moderno usa a distribuição do Azure Monitor do OpenTelemetry (Azure.Monitor.OpenTelemetry.AspNetCore). Mais pacotes de instrumentação são usados para clientes SQL, Redis e SDK do Azure. O OpenTelemetry é configurado no serviço de renderização de tíquete de exemplo do Aplicativo Web Moderno da seguinte forma:

builder.Logging.AddOpenTelemetry(o => 
{ 
    o.IncludeFormattedMessage = true; 
    o.IncludeScopes = true; 
}); 

builder.Services.AddOpenTelemetry() 
    .UseAzureMonitor(o => o.ConnectionString = appInsightsConnectionString) 
    .WithMetrics(metrics => 
    { 
        metrics.AddAspNetCoreInstrumentation() 
                .AddHttpClientInstrumentation() 
                .AddRuntimeInstrumentation(); 
    }) 
    .WithTracing(tracing => 
    { 
        tracing.AddAspNetCoreInstrumentation() 
                .AddHttpClientInstrumentation() 
                .AddSource("Azure.*"); 
    }); 

O builder.Logging.AddOpenTelemetry método roteia todo o registro em log por meio do OpenTelemetry para garantir o rastreamento e o registro em log consistentes no aplicativo. Como os serviços OpenTelemetry são registrados, builder.Services.AddOpenTelemetryo aplicativo é configurado para coletar e exportar diagnósticos, que são enviados para o Application Insights por meio de UseAzureMonitor. Além disso, a instrumentação de cliente para componentes como o Barramento de Serviço e clientes HTTP é configurada por WithMetrics e WithTracing, o que permite métricas automáticas e coleta de rastreamento sem exigir alterações no uso do cliente existente. Somente uma atualização para a configuração é necessária.

Orientações de configuração

As seções a seguir fornecem orientações sobre como implementar as atualizações de configuração. Cada seção alinha-se a um ou mais pilares do Well-Architected Framework.

Configuração Confiabilidade (RE) Segurança (SE) Otimização de custo (CO) Excelência operacional (OE) Eficiência de performance (PE) Suporte aos princípios do Well-Architected Framework
Configurar a autenticação e a autorização SE:05
OE: 10
Implementar o dimensionamento automático independente RE:06
CO: 12
PE:05
Conteinerizar a implantação de serviço CO: 13
PE:09
PE: 03

Configurar a autenticação e a autorização

Para configurar a autenticação e a autorização em quaisquer novos serviços do Azure (identidades de carga de trabalho) que você adicionar ao aplicativo Web, siga estas recomendações:

  • Use identidades gerenciadas para cada serviço novo. Cada serviço independente deve ter sua própria identidade e usar identidades gerenciadas para autenticação de serviço a serviço. As identidades gerenciadas eliminam a necessidade de gerenciar credenciais em seu código e reduzem o risco de vazamento de credenciais. Elas ajudam a evitar a inserção de informações confidenciais, como cadeias de conexão, no seu código ou arquivos de configuração.

  • Conceda privilégios mínimos a cada serviço novo. Atribua somente as permissões necessárias a cada identidade de serviço nova. Por exemplo, se uma identidade só precisar ser enviada por push para um registro de contêiner, não conceda permissões de pull. Examine essas permissões regularmente e ajuste-as conforme necessário. Use identidades diferentes para funções diferentes, como implantação e aplicativo. Isso limita o dano possível caso uma identidade seja comprometida.

  • Adote uma IaC (infraestrutura como código). Use o Bicep ou ferramentas de IaC semelhantes para definir e gerenciar seus recursos de nuvem. A IaC garante a aplicação consistente de configurações de segurança em suas implantações e permite que você controle a versão da configuração da infraestrutura.

Para configurar a autenticação e a autorização em usuários (identidades de usuário), siga estas recomendações:

  • Conceda privilégios mínimos aos usuários. Assim como acontece com os serviços, verifique se os usuários recebem apenas as permissões necessárias para executar suas tarefas. Revise e ajuste regularmente essas permissões.

  • Realize auditorias de segurança regularmente. Revise e audite regularmente sua configuração de segurança. Procure por configurações incorretas ou permissões desnecessárias e corrija-as imediatamente.

A implementação de referência usa a IaC para atribuir identidades gerenciadas a serviços adicionados e funções específicas a cada identidade. Ele define funções e permissões de acesso para implantação (containerRegistryPushRoleId), proprietário do aplicativo (containerRegistryPushRoleId) e aplicativo de Aplicativos de Contêiner (containerRegistryPullRoleId). O exemplo a seguir ilustra o código.

roleAssignments: \[
    {
    principalId: deploymentSettings.principalId
    principalType: deploymentSettings.principalType
    roleDefinitionIdOrName: containerRegistryPushRoleId
    }
    {
    principalId: ownerManagedIdentity.outputs.principal_id
    principalType: 'ServicePrincipal'
    roleDefinitionIdOrName: containerRegistryPushRoleId
    }
    {
    principalId: appManagedIdentity.outputs.principal_id
    principalType: 'ServicePrincipal'
    roleDefinitionIdOrName: containerRegistryPullRoleId
    }
\]

A implementação de referência atribui a identidade gerenciada como a nova identidade dos Aplicativos de Contêiner na implantação:

module renderingServiceContainerApp 'br/public:avm/res/app/container-app:0.1.0' = {
  name: 'application-rendering-service-container-app'
  scope: resourceGroup()
  params: {
    // Other parameters omitted for brevity.
    managedIdentities: {
      userAssignedResourceIds: [
        managedIdentity.id
      ]
    }
  }
}

Configurar o dimensionamento automático independente

O padrão do Aplicativo Web Moderno começa a dividir a arquitetura monolítica e introduz a divisão de serviço. Ao dividir uma arquitetura de aplicativo Web, você pode dimensionar serviços divididos de forma independente. Ao dimensionar os serviços do Azure para dar suporte a um serviço de aplicativo Web independente, em vez de um aplicativo Web inteiro, você otimiza os custos de dimensionamento enquanto atende às demandas. Para escalar contêineres automaticamente, siga estas recomendações:

  • Use serviços sem estado. Verifique se os serviços estão sem estado. Se o aplicativo .NET contiver o estado de sessão em processo, externalize-o para um cache distribuído como o Redis ou um banco de dados como o SQL Server.

  • Configure regras de dimensionamento automático. Use as configurações de dimensionamento automático que forneçam o controle mais econômico de seus serviços. Para serviços em contêineres, o dimensionamento baseado em eventos, como o KUBERnetes Event-Driven Autoscaler (KEDA), geralmente fornece controle granular que permite dimensionar com base nas métricas de evento. Os Aplicativos de Contêiner e o AKS dão suporte ao KEDA. Para serviços que não dão suporte ao KEDA, como o Serviço de Aplicativo, use os recursos de dimensionamento automático que a plataforma fornece. Esses recursos geralmente incluem dimensionamento baseado em regras baseadas em métricas ou tráfego HTTP.

  • Configure réplicas mínimas. Para evitar uma inicialização a frio, defina as configurações de dimensionamento automático para que mantenham no mínimo uma réplica. Um início frio ocorre quando você inicializa um serviço de um estado parado, o que geralmente cria uma resposta atrasada. Se minimizar os custos for uma prioridade e você puder tolerar atrasos de início a frio, defina a contagem mínima de réplicas como 0 ao configurar o dimensionamento automático.

  • Configure um período de resfriamento. Aplique um período de resfriamento apropriado para introduzir um atraso entre os eventos de dimensionamento. O objetivo é evitar atividades de dimensionamento excessivo disparadas por picos de carga temporários.

  • Configure o dimensionamento baseado em fila. Se o aplicativo usar uma fila de mensagens como o Barramento de Serviço, defina as configurações de dimensionamento automático para dimensionar com base no comprimento da fila com mensagens de solicitação. O dimensionador pretende manter uma réplica do serviço para cada N mensagens na fila (arredondada para cima).

Por exemplo, a implementação de referência usa o dimensionador KEDA do Barramento de Serviço para dimensionar o aplicativo de contêiner com base no comprimento da fila. O service-bus-queue-length-rule dimensiona o serviço com base no comprimento de uma fila do Barramento de Serviço especificada. O parâmetro messageCount é definido como 10, portanto, o dimensionador tem uma réplica de serviço para cada 10 mensagens na fila. Os parâmetros scaleMaxReplicas e scaleMinReplicas definem o número máximo e mínimo de réplicas para o serviço. O queue-connection-string segredo, que contém a cadeia de conexão para a fila do Barramento de Serviço, é recuperado do Azure Key Vault. Esse segredo é usado para autenticar o dimensionador no Barramento de Serviço.

scaleRules: [
  {
    name: 'service-bus-queue-length-rule'
    custom: {
      type: 'azure-servicebus'
      metadata: {
        messageCount: '10'
        namespace: renderRequestServiceBusNamespace
        queueName: renderRequestServiceBusQueueName
      }
      auth: [
        {
          secretRef: 'render-request-queue-connection-string'
          triggerParameter: 'connection'
        }
      ]
    }
  }
]

scaleMaxReplicas: 5
scaleMinReplicas: 0

Conteinerizar a implantação de serviço

Em uma implantação em contêineres, todas as dependências exigidas pelo aplicativo são encapsuladas em uma imagem leve que pode ser implantada de forma confiável em uma ampla gama de hosts. Para conteinerizar a implantação, siga estas recomendações:

  • Identifique os limites do domínio. Comece identificando os limites de domínio em seu aplicativo monolítico. Isso ajuda você a determinar quais partes do aplicativo podem ser extraídas em serviços separados.

  • Crie imagens do Docker. Ao criar imagens do Docker para seus serviços .NET, use imagens base cinzeladas. Essas imagens contêm apenas o conjunto mínimo de pacotes necessários para que o .NET seja executado, o que minimiza o tamanho do pacote e a área da superfície de ataque.

  • Use Dockerfiles de vários estágios. Implemente Dockerfiles de vários estágios para separar ativos de tempo de compilação da imagem de contêiner de tempo de execução. Usar esse tipo de arquivo ajuda a manter suas imagens de produção pequenas e seguras.

  • Execute como um usuário não root. Execute seus contêineres do .NET como um usuário não padrão (por meio de nome de usuário ou UID $APP_UID) para se alinhar com o princípio de privilégio mínimo. Isso limita os efeitos potenciais de um contêiner comprometido.

  • Escute na porta 8080. Ao executar contêineres como um usuário não padrão, configure seu aplicativo para escutar na porta 8080. Essa é uma convenção comum para usuários não desenraizamento.

  • Encapsule dependências. Verifique se todas as dependências do aplicativo estão encapsuladas na imagem de contêiner do Docker. O encapsulamento permite implantar o aplicativo de forma confiável em uma ampla gama de hosts.

  • Escolha as imagens de base certas. A imagem base escolhida depende do seu ambiente de implantação. Se você estiver implantando em Aplicativos de Contêiner, por exemplo, precisará usar imagens do Docker do Linux.

Por exemplo, a implementação de referência usa um processo de build de vários estágios. Os estágios iniciais compilam o aplicativo usando uma imagem completa do SDK (mcr.microsoft.com/dotnet/sdk:8.0-jammy). A imagem final do runtime é criada a partir da imagem base chiseled, o que exclui o SDK e os artefatos de build. O serviço é executado como um usuário não raiz (USER $APP_UID) e expõe a porta 8080. As dependências necessárias para o aplicativo operar são incluídas na imagem do Docker, conforme evidenciado pelos comandos para copiar arquivos de projeto e restaurar pacotes. O uso de imagens baseadas em Linux (mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled) garante a compatibilidade com Aplicativos de Contêiner, que exigem contêineres do Linux para implantação.

# Build in a separate stage to avoid copying the SDK into the final image.
FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src

# Restore packages.
COPY ["Relecloud.TicketRenderer/Relecloud.TicketRenderer.csproj", "Relecloud.TicketRenderer/"]
COPY ["Relecloud.Messaging/Relecloud.Messaging.csproj", "Relecloud.Messaging/"]
COPY ["Relecloud.Models/Relecloud.Models.csproj", "Relecloud.Models/"]
RUN dotnet restore "./Relecloud.TicketRenderer/Relecloud.TicketRenderer.csproj"

# Build and publish.
COPY . .
WORKDIR "/src/Relecloud.TicketRenderer"
RUN dotnet publish "./Relecloud.TicketRenderer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

# Chiseled images contain only the minimal set of packages needed for .NET 8.0.
FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled AS final
WORKDIR /app
EXPOSE 8080

# Copy the published app from the build stage.
COPY --from=build /app/publish .

# Run as nonroot user.
USER $APP_UID
ENTRYPOINT ["dotnet", "./Relecloud.TicketRenderer.dll"]

Implantar a implementação de referência

Implante a implementação de referência do Padrão de Aplicativo Web Moderno para .NET. Há instruções para implantação de desenvolvimento e produção no repositório. Depois de implantar a implementação, você pode simular e observar padrões de design.

O diagrama a seguir mostra a arquitetura da implementação de referência:

Diagrama mostrando a arquitetura da implementação de referência.

Baixe um arquivo do Visio dessa arquitetura.