Editar

Compartilhar via


Padrão de aplicativo Web moderno para Java

Serviço de aplicativo do Azure
Barramento de Serviço do Azure

Este artigo descreve como implementar o padrão de Aplicativo Web Moderno. O padrão de Aplicativo Web Moderno define como modernizar aplicativos Web de nuvem e introduzir uma arquitetura orientada a serviços. O padrão fornece diretrizes prescritivas de arquitetura, código e configuração que se alinham aos princípios do do Azure Well-Architected Framework. Esse padrão se baseia no padrão Reliable Web App.

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

O padrão de Aplicativo Web Moderno ajuda você a otimizar áreas de alta demanda do seu aplicativo Web. Ele fornece diretrizes detalhadas para desassociar essas áreas para habilitar o dimensionamento independente para otimização de custos. Essa abordagem permite alocar recursos dedicados a componentes críticos, o que melhora o desempenho geral. A desassociação de serviços separaveis pode melhorar a confiabilidade, evitando que a desaceleração em uma parte do aplicativo afete outras pessoas. Ele também habilita o controle de versão independente de componentes de aplicativo individuais.

Como implementar o padrão de Aplicativo Web Moderno

Este artigo contém diretrizes para implementar o padrão de Aplicativo Web Moderno. Use os links a seguir para acessar as diretrizes específicas de que você precisa:

  • de diretrizes de arquitetura de . Saiba como modularizar componentes de aplicativo Web e selecionar soluções de PaaS (plataforma como serviço) apropriadas.
  • de 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.
  • de diretrizes de configuração de . 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 de implementação de referência (aplicativo de exemplo) do padrão de Aplicativo Web Moderno. Ele representa o estado final da implementação do Aplicativo Web Moderno. É 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. Ele requer alguns componentes de arquitetura adicionais. Você precisa de uma fila de mensagens, plataforma de contêiner, serviço de armazenamento e registro de contêiner, conforme mostrado no diagrama a seguir:

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. Configure o balanceador de carga para rotear o tráfego para a segunda região para dar suporte a uma configuração ativa-ativa ou ativa-passiva, dependendo das suas necessidades comerciais. As duas regiões exigem os mesmos serviços, exceto que uma região tem uma rede virtual de hub. 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 mostra esta arquitetura:

Diagrama mostrando a arquitetura de padrão do Aplicativo Web Moderno com uma segunda região.

Desacoplar a arquitetura

Para implementar o padrão de Aplicativo Web Moderno, você precisa dividir a arquitetura de aplicativo Web existente. Desassociar a arquitetura envolve dividir um aplicativo monolítico em serviços menores e independentes, cada um responsável por um recurso ou função específico. Esse processo inclui a avaliação do aplicativo Web atual, a modificação da arquitetura e, por fim, a 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:

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

  • Avalie os benefícios do serviço. Concentre-se nos serviços que mais se beneficiam do dimensionamento independente. Por exemplo, uma dependência externa, como um provedor de serviços de email em um aplicativo LOB, pode exigir mais isolamento contra falhas. Considere os serviços que passam por atualizações ou alterações frequentes. A desassociação desses serviços permite a implantação independente e reduz o risco de afetar outras partes do aplicativo.

  • 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 como gerenciar e compartilhar dados entre 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 a seção Selecionar os serviços do Azure corretos deste artigo.

  • Desacoplar o serviço de aplicativo Web. Defina interfaces e APIs claras que os serviços de aplicativo Web recém-extraídos podem usar para interagir com outras partes do sistema. Crie uma estratégia de gerenciamento de dados que permita que cada serviço gerencie seus próprios dados, mas garanta 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 diretrizes de código .

  • Use o armazenamento independente para serviços divididos. Para simplificar o controle de versão e a implantação, verifique se cada serviço separado tem seus próprios armazenamentos de dados. Por exemplo, a implementação de referência separa o serviço de email do aplicativo Web e elimina a necessidade de o serviço acessar o banco de dados. Em vez disso, o serviço comunica o status de entrega de email de volta ao aplicativo Web por meio de uma mensagem do Barramento de Serviço do Azure e o aplicativo Web salva uma observação em seu banco de dados.

  • Implemente pipelines de implantação separados para cada serviço dividido. Se você implementar pipelines de implantação separados, cada serviço poderá ser atualizado de acordo com sua própria agenda. Se equipes ou organizações diferentes em sua empresa possuem serviços diferentes, o uso de pipelines de implantação separados fornece 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. Comece com o Barramento de Serviço e use uma das 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 nos quais muitos eventos pequenos e 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 nos quais os dados em massa precisam ser ingeridos e processados continuamente.
  • Implemente um serviço de contêiner. Para os elementos do seu aplicativo que você deseja colocar em contêineres, você precisa de uma plataforma de aplicativo que dê suporte a contêineres. As diretrizes de Escolher um serviço de contêiner do Azure podem ajudá-lo a selecionar um. 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 e use uma das 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.
    Aplicativo 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 um 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 no Azure orientação pode ajudá-lo a escolher um.

Orientações de código

Para desacoplar e extrair com êxito um serviço independente, você precisa atualizar o código do aplicativo Web com os seguintes padrões de design: Strangler Fig, Queue-Based Load Leveling, Competing Consumers, Health Endpoint Monitoring e Retry. O diagrama a seguir mostra as funções desses padrões:

Diagrama mostrando a função 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 parte do produtor do serviço desacoplado para gerenciar o fluxo de mensagens de forma assíncrona usando uma fila.

  3. de padrão consumidores concorrentes: o padrão Consumidores Concorrentes permite que várias instâncias de um serviço desacoplado leiam independentemente da mesma fila de mensagens e competem para processar mensagens. Implemente esse padrão no serviço dividido para distribuir tarefas em várias instâncias.

  4. padrão de Monitoramento de Ponto de Extremidade de Integridade: o padrão monitoramento de ponto de extremidade de integridade expõe pontos de extremidade para monitorar o status e a integridade de diferentes componentes 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) Implementar esse padrão no aplicativo Web principal, em todas as chamadas de saída para outros serviços do Azure, como chamadas para a 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 dos pilares do Well-Architected Framework. A tabela a seguir fornece 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 Produtor de serviço dividido RE:06
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.Implemente 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 desacoplados 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 serviço.

    Por exemplo, a implementação de referência extrai a funcionalidade de entrega de email em um serviço autônomo. O serviço pode ser introduzido gradualmente para lidar com uma porcentagem maior das solicitações para enviar emails que contêm guias de suporte da Contoso. À medida que o novo serviço prova sua confiabilidade e desempenho, ele pode eventualmente assumir todo o conjunto de responsabilidades de e-mail do monólito, completando 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). Verifique se o novo serviço desacoplado pode lidar com solicitações de forma independente quando ele é 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 desacoplado 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 o serviço desacoplado manipular mensagens na fila. Se o processo exigir o resultado da operação de serviço desacoplado, implemente uma maneira alternativa de lidar com a situação enquanto aguarda a conclusão da operação enfileirada. Por exemplo, no Spring Boot, você pode usar a classe StreamBridge para publicar mensagens de forma assíncrona na fila sem bloquear o thread de chamada:

    private final StreamBridge streamBridge;
    
    public SupportGuideQueueSender(StreamBridge streamBridge) {
        this.streamBridge = streamBridge;
    }
    
    // Asynchronously publish a message without blocking the calling thread
    @Override
    public void send(String to, String guideUrl, Long requestId) {
        EmailRequest emailRequest = EmailRequest.newBuilder()
                .setRequestId(requestId)
                .setEmailAddress(to)
                .setUrlToManual(guideUrl)
                .build();
    
        log.info("EmailRequest: {}", emailRequest);
    
        var message = emailRequest.toByteArray();
        streamBridge.send(EMAIL_REQUEST_QUEUE, message);
    
        log.info("Message sent to the queue");
    }
    

    Este exemplo Java usa StreamBridge para enviar mensagens de forma assíncrona. Essa abordagem garante que o aplicativo principal permaneça responsivo e possa lidar com outras tarefas simultaneamente enquanto o serviço desacoplado 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. No Spring Boot, você pode usar @StreamListener ou @KafkaListener com um identificador de mensagem exclusivo para evitar o processamento duplicado. Ou você pode organizar o processo de negócios para operar em uma abordagem funcional com o Spring Cloud Stream, em que o consume método é definido de uma forma que produz o mesmo resultado quando é executado repetidamente. Para obter uma lista das configurações que gerenciam o comportamento do consumo de mensagens, consulte Spring Cloud Stream com o Barramento de Serviço.

  • Gerenciar alterações na experiência do usuário. Quando você usa o processamento assíncrono, as tarefas podem não ser concluídas imediatamente. Para definir expectativas e evitar confusão, verifique se os usuários sabem quando suas tarefas ainda estão sendo processadas. 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 no serviço desacoplado 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. O padrão aprimora o balanceamento de carga e aumenta a capacidade do sistema para 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 após o aplicativo inicial.

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

  • Lide com mensagens simultâneas. Quando os serviços recebem mensagens de uma fila, verifique se o sistema é dimensionado previsivelmente configurando a simultaneidade para corresponder ao design do sistema. Os resultados do teste de carga podem ajudá-lo a determinar o número apropriado de mensagens simultâneas a serem tratadas. Você pode começar de um para medir o desempenho do sistema.

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

  • Use modos confiáveis de processamento de mensagens. Use um modo de processamento confiável, como Peek-Lock, que tenta 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 mensagens problemáticas para a fila separada. Com o Barramento de Serviço, as mensagens são movidas para a fila de letreiro morto após um número especificado de tentativas de entrega ou após rejeição explícita pelo aplicativo.

  • Lide com mensagens fora de ordem. Projete os consumidores para que processem mensagens que chegam fora de sequência. Quando você tem vários consumidores paralelos, eles podem processar mensagens fora de ordem.

  • Dimensione com base no comprimento da fila. Os serviços que consomem mensagens de uma fila devem dimensionar automaticamente com base no comprimento da fila. O dimensionamento automático baseado em escala permite o processamento eficiente de 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 fila de resposta ou resposta dedicada. Essa configuração separa o sistema de 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. Isso permite 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.

A implementação de referência usa o padrão Consumidores Concorrentes em um serviço sem estado executado em Aplicativos de Contêiner para processar solicitações de entrega de email de uma fila do Barramento de Serviço.

O processador registra detalhes de processamento de mensagens para ajudar na solução de problemas e monitoramento. Ele captura erros de desserialização e fornece insights que podem ser úteis durante a depuração. O serviço é dimensionado no nível do contêiner para habilitar a manipulação eficiente de picos de mensagens com base no comprimento da fila. Este é o código:

@Configuration
public class EmailProcessor {

    private static final Logger log = LoggerFactory.getLogger(EmailProcessor.class);

    @Bean
    Function<byte[], byte[]> consume() {
        return message -> {

            log.info("New message received");

            try {
                EmailRequest emailRequest = EmailRequest.parseFrom(message);
                log.info("EmailRequest: {}", emailRequest);

                EmailResponse emailResponse = EmailResponse.newBuilder()
                        .setEmailAddress(emailRequest.getEmailAddress())
                        .setUrlToManual(emailRequest.getUrlToManual())
                        .setRequestId(emailRequest.getRequestId())
                        .setMessage("Email sent to " + emailRequest.getEmailAddress() + " with URL to manual " + emailRequest.getUrlToManual())
                        .setStatus(Status.SUCCESS)
                        .build();

                return emailResponse.toByteArray();

            } catch (InvalidProtocolBufferException e) {
                throw new RuntimeException("Error parsing email request message", e);
            }
        };
    }
}

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. O Spring Boot Actuator fornece suporte interno para verificações de integridade. Ele pode expor pontos de extremidade de verificação de integridade para dependências de chave, como bancos de dados, agentes de mensagens e sistemas de armazenamento. Para implementar o padrão Monitoramento de Ponto de Extremidade de Integridade, siga estas recomendações:

  • Implemente verificações de integridade. Use o Spring Boot Actuator para fornecer endpoints de verificação de integridade. O Actuator expõe um ponto de extremidade /actuator/health que inclui indicadores de integridade internos e verificações personalizadas para várias dependências. Para habilitar o ponto de extremidade de integridade, adicione a dependência spring-boot-starter-actuator no arquivo pom.xml ou build.gradle:

    <!-- Add Spring Boot Actuator dependency -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    

    Configure o ponto de extremidade de integridade no application.properties conforme mostrado na implementação de referência:

        management.endpoints.web.exposure.include=metrics,health,info,retry,retryevents
    
  • Valide dependências. O Spring Boot Actuator inclui indicadores de integridade para várias dependências, como bancos de dados, agentes de mensagens (RabbitMQ ou Kafka) e serviços de armazenamento. Para validar a disponibilidade de serviços do Azure, como o Armazenamento de Blobs do Azure ou o Barramento de Serviço, use tecnologias como o Azure Spring Apps ou o Micrometer, que fornecem indicadores de integridade para esses serviços. Se você precisar de verificações personalizadas, poderá implementá-las criando um bean HealthIndicator personalizado:

    import org.springframework.boot.actuate.health.Health;
    import org.springframework.boot.actuate.health.HealthIndicator;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CustomAzureServiceBusHealthIndicator implements HealthIndicator {
        @Override
        public Health health() {
            // Implement your health check logic here (for example, ping Service Bus).
            boolean isServiceBusHealthy = checkServiceBusHealth();
            return isServiceBusHealthy ? Health.up().build() : Health.down().build();
        }
    
        private boolean checkServiceBusHealth() {
            // Implement health check logic (pinging or connecting to the service).
            return true; // Placeholder. Implement the actual logic.
        }
    }
    
  • Configure implantes do Azure. Configure o recurso do Azure para usar as URLs de verificação de integridade do aplicativo para confirmar a atividade e a preparação. Por exemplo, você pode usar o Terraform para confirmar a disponibilidade e a preparação dos aplicativos implantados nos Aplicativos de Contêiner. Para obter mais informações, consulte Investigações de integridade em Aplicativos de Contêiner.

Implementar o padrão Repetição

O padrão de repetição permite que os aplicativos se recuperem de falhas transitórias. Esse padrão é central para o padrão de Aplicativo Web Confiável, portanto, seu aplicativo Web já deve estar usando o padrão de repetição. Aplique o padrão de repetição às solicitações aos sistemas de mensagens e 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. Certifique-se de configurar o cliente responsável pelas interações com a fila de mensagens 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 a 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. Esses mecanismos internos são otimizados para os casos de uso típicos do serviço, podem lidar com novas tentativas com mais eficiência e exigem menos configuração.

  • Use bibliotecas de resiliência padrão para clientes HTTP. Para clientes HTTP, você pode usar Resiliência4j junto com RestTemplate ou WebClient do Spring para lidar com novas tentativas em comunicações HTTP. Você pode encapsular RestTemplate com a lógica de repetição do Resilience4j para lidar com erros HTTP transitórios com eficiência.

  • 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. Por exemplo, use modos de bloqueio de espiada quando estiverem disponíveis. 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.

Orientações de configuração

As seções a seguir fornecem diretrizes para implementar as atualizações de configuração. Cada seção se alinha a um ou mais dos 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. Eles ajudam você a evitar a inclusão de informações confidenciais, como cadeias de conexão em 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 potencial se uma identidade estiver comprometida.

  • Use a infraestrutura como código (IaC). Use o Bicep ou uma ferramenta IaC semelhante, como o Terraform, 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 têm 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 configurações incorretas e permissões desnecessárias e retificar ou removê-las 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 o acesso de funções e permissões para implantação definindo funções para pushes e pulls do Registro de Contêiner. Este é o código:

resource "azurerm_role_assignment" "container_app_acr_pull" {
  principal_id         = var.aca_identity_principal_id
  role_definition_name = "AcrPull"
  scope                = azurerm_container_registry.acr.id
}

resource "azurerm_user_assigned_identity" "container_registry_user_assigned_identity" {
  name                = "ContainerRegistryUserAssignedIdentity"
  resource_group_name = var.resource_group
  location            = var.location
}

resource "azurerm_role_assignment" "container_registry_user_assigned_identity_acr_pull" {
  scope                = azurerm_container_registry.acr.id
  role_definition_name = "AcrPull"
  principal_id         = azurerm_user_assigned_identity.container_registry_user_assigned_identity.principal_id
}


# For demo purposes, allow the current user to access the container registry.
# Note: When running as a service principal, this is also needed.
resource "azurerm_role_assignment" "acr_contributor_user_role_assignement" {
  scope                = azurerm_container_registry.acr.id
  role_definition_name = "Contributor"
  principal_id         = data.azuread_client_config.current.object_id
}

Configurar o dimensionamento automático independente

O padrão aplicativo Web moderno começa a quebrar a arquitetura monolítica e apresenta a desacoplamento do 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 Web 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 fornecidos pela própria plataforma. Esses recursos geralmente incluem dimensionamento com base em regras baseadas em métricas ou tráfego HTTP.

  • Configure réplicas mínimas. Para evitar inícios frios, defina as configurações de dimensionamento automático para manter um mínimo de uma réplica. Um início frio é a inicialização de um serviço de um estado parado. Um início frio geralmente atrasa a resposta. 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 excessivas acionadas 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 de mensagens de solicitação. O dimensionador tenta 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 automaticamente o Aplicativo de Contêiner com base no comprimento da fila do Barramento de Serviço. A regra de dimensionamento, chamada service-bus-queue-length-rule, ajusta o número de réplicas de serviço com base na contagem de mensagens na fila do Barramento de Serviço especificada. O parâmetro messageCount é definido como 10, o que configura o dimensionador para adicionar uma réplica para cada 10 mensagens na fila. A contagem máxima de réplicas (max_replicas) é definida como 10. A contagem mínima de réplicas é implicitamente 0, a menos que seja substituída. Essa configuração permite que o serviço reduza verticalmente para zero quando não houver mensagens na fila. A cadeia de conexão da fila do Barramento de Serviço é armazenada como um segredo no Azure, chamado azure-servicebus-connection-string, que é usado para autenticar o dimensionador no Barramento de Serviço. Aqui está o código do Terraform:

    max_replicas = 10
    min_replicas = 1

    custom_scale_rule {
      name             = "service-bus-queue-length-rule"
      custom_rule_type = "azure-servicebus"
      metadata = {
        messageCount = 10
        namespace    = var.servicebus_namespace
        queueName    = var.email_request_queue_name
      }
      authentication {
        secret_name       = "azure-servicebus-connection-string"
        trigger_parameter = "connection"
      }
    }

Conteinerizar a implantação de serviço

A contêinerização é o encapsulamento de todas as dependências necessárias para o aplicativo 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 você pode extrair em serviços separados.

  • Crie imagens do Docker. Ao criar imagens do Docker para seus serviços Java, use imagens base oficiais do OpenJDK. Essas imagens contêm apenas o conjunto mínimo de pacotes que o Java precisa executar. O uso dessas imagens minimiza o tamanho do pacote e a área da superfície de ataque.

  • Use Dockerfiles de vários estágios. Use um Dockerfile de vários estágios para separar ativos de tempo de build da imagem de contêiner de runtime. Usar esse tipo de arquivo ajuda a manter suas imagens de produção pequenas e seguras. Você também pode usar um servidor de build pré-configurado e copiar o arquivo JAR para a imagem de contêiner.

  • Execute como um usuário não root. Execute seus contêineres Java como um usuário não padrão (por meio de nome de usuário ou UID $APP_UID) para se alinhar ao 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 de que o aplicativo precisa estão encapsuladas na imagem de contêiner do Docker. O encapsulamento permite que o aplicativo seja implantado de forma confiável em uma série de hosts.

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

A implementação de referência demonstra um processo de construção do Docker para conteinerizar um aplicativo Java. O Dockerfile usa um build de estágio único com a imagem base do OpenJDK (mcr.microsoft.com/openjdk/jdk:17-ubuntu), que fornece o ambiente de runtime do Java necessário.

O Dockerfile inclui as seguintes etapas:

  1. Declarando o volume. Um volume temporário (/tmp) é definido. Esse volume fornece armazenamento temporário de arquivos separado do sistema de arquivos principal do contêiner.
  2. Copiando artefatos. O arquivo JAR do aplicativo (email-processor.jar) é copiado para o contêiner, juntamente com o agente do Application Insights (applicationinsights-agent.jar) usado para monitoramento.
  3. Definindo o ponto de entrada. O contêiner é configurado para executar o aplicativo com o agente do Application Insights habilitado. O código usa java -javaagent para monitorar o aplicativo durante o runtime.

O Dockerfile mantém a imagem pequena apenas incluindo dependências de runtime. Ele é adequado para ambientes de implantação como Aplicativos de Contêiner que dão suporte a contêineres baseados em Linux.

# Use the OpenJDK 17 base image on Ubuntu as the foundation.
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu

# Define a volume to allow temporary files to be stored separately from the container's main file system.
VOLUME /tmp

# Copy the packaged JAR file into the container.
COPY target/email-processor.jar app.jar

# Copy the Application Insights agent for monitoring.
COPY target/agent/applicationinsights-agent.jar applicationinsights-agent.jar

# Set the entrypoint to run the application with the Application Insights agent.
ENTRYPOINT ["java", "-javaagent:applicationinsights-agent.jar", "-jar", "/app.jar"]

Implantar a implementação de referência

Implante a implementação de referência do Padrão de Aplicativo Web Moderno para Java. 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.