Modello di app Web moderno per Java

Servizio app di Azure
Bus di servizio di Azure

Questo articolo descrive come implementare il modello di app Web moderna. Il modello Di app Web moderna definisce come modernizzare le app Web cloud e introdurre un'architettura orientata ai servizi. Il modello fornisce indicazioni sull'architettura, il codice e la configurazione prescrittive che si allineano ai principi di Azure Well-Architected Framework. Questo modello si basa sul modello Reliable Web App.

Perché usare il modello di app Web moderna?

Il modello Di app Web moderna consente di ottimizzare le aree a domanda elevata dell'applicazione Web. Fornisce indicazioni dettagliate per separare queste aree per abilitare la scalabilità indipendente per l'ottimizzazione dei costi. Questo approccio consente di allocare risorse dedicate a componenti critici, migliorando così le prestazioni complessive. Il disaccoppiamento dei servizi separabili può migliorare l'affidabilità impedendo che i rallentamenti in una parte dell'app influiscano sugli altri. Consente anche il controllo delle versioni indipendente dei singoli componenti dell'app.

Come implementare il modello di app Web moderna

Questo articolo contiene indicazioni per l'implementazione del modello di app Web moderna. Usare i collegamenti seguenti per passare alle indicazioni specifiche necessarie:

  • linee guida per l'architettura . Informazioni su come modularizzare i componenti delle app Web e selezionare le soluzioni PaaS (Platform as a Service) appropriate.
  • Linee guida per il codice. Implementare quattro modelli di progettazione per ottimizzare i componenti disaccoppiati: Strangler Fig, Queue-Based il livellamento del carico, i consumer concorrenti e il monitoraggio degli endpoint di integrità.
  • indicazioni sulla configurazione. Configurare l'autenticazione, l'autorizzazione, la scalabilità automatica e la containerizzazione per i componenti disaccoppiati.

Suggerimento

logo GitHub È disponibile un 'implementazione di riferimento (app di esempio) del modello di app Web moderna. Rappresenta lo stato finale dell'implementazione dell'app Web moderna. Si tratta di un'app Web di livello di produzione che include tutti gli aggiornamenti di codice, architettura e configurazione descritti in questo articolo. Distribuire e usare l'implementazione di riferimento per guidare l'implementazione del modello di app Web moderna.

Linee guida per l'architettura

Il modello di app Web moderna si basa sul modello Reliable Web App. Richiede alcuni componenti architetturali aggiuntivi. È necessaria una coda di messaggi, una piattaforma contenitore, un servizio di archiviazione e un registro contenitori, come illustrato nel diagramma seguente:

Diagramma che mostra l'architettura di base del modello di app Web moderna.

Per un obiettivo di livello di servizio (SLO) superiore, è possibile aggiungere una seconda area all'architettura dell'app Web. Configurare il servizio di bilanciamento del carico per instradare il traffico alla seconda area per supportare una configurazione attiva-attiva o passiva attiva, a seconda delle esigenze aziendali. Le due aree richiedono gli stessi servizi, ad eccezione di un'area con una rete virtuale hub. Usare una topologia di rete hub-spoke per centralizzare e condividere risorse, ad esempio un firewall di rete. Accedere al repository di contenitori tramite la rete virtuale hub. Se si dispone di macchine virtuali, aggiungere un bastion host alla rete virtuale hub per gestirli con sicurezza avanzata. Il diagramma seguente illustra questa architettura:

Diagramma che mostra l'architettura del modello di app Web moderna con una seconda area.

Separare l'architettura

Per implementare il modello di app Web moderna, è necessario separare l'architettura dell'app Web esistente. La separazione dell'architettura comporta la suddivisione di un'applicazione monolitica in servizi più piccoli e indipendenti, ognuno responsabile di una funzionalità o di una funzione specifica. Questo processo include la valutazione dell'app Web corrente, la modifica dell'architettura e infine l'estrazione del codice dell'app Web in una piattaforma contenitore. L'obiettivo è identificare ed estrarre sistematicamente i servizi dell'applicazione che traggono vantaggio dalla separazione. Per separare l'architettura, seguire queste indicazioni:

  • Identificare i limiti del servizio. Applicare i principi di progettazione basati su dominio per identificare i contesti delimitati all'interno dell'applicazione monolitica. Ogni contesto delimitato rappresenta un limite logico ed è un candidato per la disaccoppiamento. I servizi che rappresentano funzioni aziendali distinte e hanno meno dipendenze sono candidati validi.

  • Valutare i vantaggi del servizio. Concentrarsi sui servizi che traggono vantaggio dalla scalabilità indipendente. Ad esempio, una dipendenza esterna come un provider di servizi di posta elettronica in un'applicazione LINEB potrebbe richiedere un maggiore isolamento da un errore. Prendere in considerazione i servizi che subiscono aggiornamenti o modifiche frequenti. La disaccoppiamento di questi servizi consente la distribuzione indipendente e riduce il rischio di influire su altre parti dell'applicazione.

  • Valutare la fattibilità tecnica. Esaminare l'architettura corrente per identificare vincoli tecnici e dipendenze che potrebbero influire sul processo di disaccoppiamento. Pianificare la gestione e la condivisione dei dati tra servizi. I servizi disaccoppiati devono gestire i propri dati e ridurre al minimo l'accesso diretto al database attraverso i limiti del servizio.

  • Distribuire i servizi di Azure. Selezionare e distribuire i servizi di Azure necessari per supportare il servizio app Web che si intende estrarre. Per indicazioni, vedere la sezione Selezionare i servizi di Azure corretti di questo articolo.

  • Separare il servizio app Web. Definire interfacce e API chiare che i servizi di app Web appena estratti possono usare per interagire con altre parti del sistema. Progettare una strategia di gestione dei dati che consente a ogni servizio di gestire i propri dati, ma garantisce coerenza e integrità. Per strategie di implementazione specifiche e modelli di progettazione da usare durante questo processo di estrazione, vedere la sezione Linee guida sul codice.

  • Usare l'archiviazione indipendente per i servizi disaccoppiati. Per semplificare il controllo delle versioni e la distribuzione, assicurarsi che ogni servizio disaccoppiato disponga di archivi dati propri. Ad esempio, l'implementazione di riferimento separa il servizio di posta elettronica dall'app Web ed elimina la necessità del servizio di accedere al database. Il servizio comunica invece lo stato di recapito della posta elettronica all'app Web tramite un messaggio di bus di servizio di Azure e l'app Web salva una nota nel relativo database.

  • Implementare pipeline di distribuzione separate per ogni servizio disaccoppiato. Se si implementano pipeline di distribuzione separate, ogni servizio può essere aggiornato in base alla propria pianificazione. Se team o organizzazioni diverse all'interno dell'azienda possiedono servizi diversi, l'uso di pipeline di distribuzione separate offre a ogni team il controllo sulle proprie distribuzioni. Usare strumenti di integrazione continua e recapito continuo (CI/CD) come Jenkins, GitHub Actions o Azure Pipelines per configurare queste pipeline.

  • Rivedere i controlli di sicurezza. Assicurarsi che i controlli di sicurezza vengano aggiornati per tenere conto della nuova architettura, incluse le regole del firewall e i controlli di accesso.

Selezionare i servizi di Azure corretti

Per ogni servizio di Azure nell'architettura, vedere la guida pertinente al servizio di Azure in Well-Architected Framework. Per il modello di app Web moderna, è necessario un sistema di messaggistica per supportare la messaggistica asincrona, una piattaforma applicativa che supporta la containerizzazione e un repository di immagini del contenitore.

  • Scegliere una coda di messaggi. Una coda di messaggi è un componente importante delle architetture orientate ai servizi. Separa i mittenti e i ricevitori dei messaggi per abilitare la messaggistica asincrona. Usare le indicazioni sulla scelta di un servizio di messaggistica di Azure per scegliere un sistema di messaggistica di Azure che supporti le esigenze di progettazione. Azure include tre servizi di messaggistica: Griglia di eventi di Azure, Hub eventi di Azure e bus di servizio. Iniziare con il bus di servizio e usare una delle altre due opzioni se il bus di servizio non soddisfa le proprie esigenze.

    Service Caso d'uso
    Bus di servizio Scegliere bus di servizio per il recapito affidabile, ordinato ed eventualmente transazionale di messaggi di alto valore nelle applicazioni aziendali.
    Griglia di eventi Scegliere Griglia di eventi quando è necessario gestire in modo efficiente un numero elevato di eventi discreti. Griglia di eventi è scalabile per le applicazioni guidate dagli eventi in cui è necessario instradare molti eventi indipendenti di piccole dimensioni (ad esempio le modifiche dello stato delle risorse) ai sottoscrittori in un modello di pubblicazione-sottoscrizione a bassa latenza.
    Hub eventi di Scegliere Hub eventi per l'inserimento di dati con velocità effettiva elevata elevata, ad esempio dati di telemetria, log o analisi in tempo reale. Hub eventi è ottimizzato per scenari di streaming in cui i dati in blocco devono essere inseriti ed elaborati continuamente.
  • Implementare un servizio contenitore. Per gli elementi dell'applicazione da inserire in contenitori, è necessaria una piattaforma applicativa che supporta i contenitori. Il Scegliere un servizio Azure Container indicazioni può essere utile per selezionare uno. Azure include tre servizi contenitore principali: App azure Container, servizio Azure Kubernetes (AKS) e servizio app Azure. Iniziare con App contenitore e usare una delle altre due opzioni se App contenitore non soddisfa le proprie esigenze.

    Service Caso d'uso
    App contenitore Scegliere App contenitore se è necessaria una piattaforma serverless che ridimensiona e gestisce automaticamente i contenitori nelle applicazioni guidate dagli eventi.
    servizio Azure Kubernetes Scegliere il servizio Azure Kubernetes se è necessario un controllo dettagliato sulle configurazioni di Kubernetes e sulle funzionalità avanzate per la scalabilità, la rete e la sicurezza.
    App Web per contenitori Scegliere App Web per contenitori nel servizio app per l'esperienza PaaS più semplice.
  • Implementare un repository di contenitori. Quando si usa un servizio di calcolo basato su contenitori, è necessario disporre di un repository per archiviare le immagini del contenitore. È possibile usare un registro contenitori pubblico, ad esempio Docker Hub o un registro gestito, ad esempio Registro Azure Container. Le linee guida Introduzione ai registri contenitori in Azure consentono di sceglierne una.

Linee guida per il codice

Per separare ed estrarre correttamente un servizio indipendente, è necessario aggiornare il codice dell'app Web con i modelli di progettazione seguenti: Strangler Fig, Queue-Based livellamento del carico, Consumer concorrenti, Monitoraggio endpoint integrità e Riprova. Il diagramma seguente illustra i ruoli di questi modelli:

Diagramma che mostra il ruolo dei modelli di progettazione nell'architettura del modello di app Web moderna.

  1. Modello Fig strangler: il modello Strangler Fig esegue la migrazione incrementale delle funzionalità da un'applicazione monolitica al servizio disaccoppiato. Implementare questo modello nell'app Web principale per eseguire gradualmente la migrazione delle funzionalità ai servizi indipendenti indirizzando il traffico in base agli endpoint.

  2. Modello di livellamento del carico basato su coda: il modello di livellamento del carico basato su coda gestisce il flusso di messaggi tra il producer e il consumer usando una coda come buffer. Implementare questo modello nella parte producer del servizio disaccoppiato per gestire il flusso dei messaggi in modo asincrono usando una coda.

  3. modello Consumer concorrenti: il modello Consumer concorrenti consente a più istanze di un servizio disaccoppiato di leggere in modo indipendente dalla stessa coda di messaggi e competere per elaborare i messaggi. Implementare questo modello nel servizio disaccoppiato per distribuire le attività tra più istanze.

  4. modello di monitoraggio degli endpoint di integrità: il modello di monitoraggio degli endpoint di integrità espone gli endpoint per il monitoraggio dello stato e dell'integrità di diversi componenti dell'app Web. (4a) Implementare questo modello nell'app Web principale. (4b) Implementarlo anche nel servizio disaccoppiato per tenere traccia dell'integrità degli endpoint.

  5. Modello di ripetizione dei tentativi: il modello di ripetizione dei tentativi gestisce gli errori temporanei ritentando le operazioni che potrebbero non riuscire in modo intermittente. (5a) Implementare questo modello nell'app Web principale, in tutte le chiamate in uscita ad altri servizi di Azure, ad esempio le chiamate alla coda dei messaggi e agli endpoint privati. (5b) Implementare anche questo modello nel servizio disaccoppiato per gestire gli errori temporanei nelle chiamate agli endpoint privati.

Ogni modello di progettazione offre vantaggi che si allineano a uno o più dei pilastri del framework di Well-Architected. Nella tabella seguente vengono forniti i dettagli.

Schema progettuale Percorso di implementazione Affidabilità (RE) Sicurezza (SE) Ottimizzazione costi (CO) Eccellenza operativa (OE) Efficienza delle prestazioni (PE) Supporto dei principi del framework ben progettato
Modello Fig strangler App Web principale RE:08
CO:07
CO:08
OE:06
OE:11
Schema di livellamento del carico basato sulle code Produttore di un servizio disaccoppiato RE:06
RE:07
CO:12
PE:05
Modello consumer concorrenti Servizio disaccoppiato RE:05
RE:07
CO:05
CO:07
PE:05
PE:07
Modello di monitoraggio degli endpoint di integrità App Web principale e servizio disaccoppiato RE:07
RE:10
OE:07
PE:05
Modello di ripetizione dei tentativi App Web principale e servizio disaccoppiato RE:07

Implementare il modello Strangler Fig

Usare il modello Strangler Fig per eseguire gradualmente la migrazione delle funzionalità dalla codebase monolitica ai nuovi servizi indipendenti. Estrarre nuovi servizi dalla codebase monolitica esistente e modernizzare lentamente le parti critiche dell'app Web. Per implementare il modello Strangler Fig, seguire queste indicazioni:

  • Configurare un livello di routing. Nella codebase dell'app Web monolitica implementare un livello di routing che indirizza il traffico in base agli endpoint. Usare la logica di routing personalizzata in base alle esigenze per gestire regole business specifiche per indirizzare il traffico. Ad esempio, se si dispone di un endpoint /users nell'app monolitica e si sposta tale funzionalità nel servizio disaccoppiato, il livello di routing indirizza tutte le richieste a /users al nuovo servizio.

  • Gestire l'implementazione delle funzionalità.Implementare flag di funzionalità e implementazione a fasi per implementare gradualmente i servizi disaccoppiati. Il routing monolitico dell'app esistente deve controllare il numero di richieste ricevute dai servizi disaccoppiati. Iniziare con una piccola percentuale di richieste e aumentare l'utilizzo nel tempo man mano che si ottiene fiducia nella stabilità e nelle prestazioni del servizio.

    Ad esempio, l'implementazione di riferimento estrae la funzionalità di recapito della posta elettronica in un servizio autonomo. Il servizio può essere introdotto gradualmente per gestire una percentuale maggiore delle richieste di invio di messaggi di posta elettronica contenenti guide al supporto di Contoso. Poiché il nuovo servizio dimostra l'affidabilità e le prestazioni, alla fine può assumere l'intero set di responsabilità di posta elettronica dal monolith, completando la transizione.

  • Usare un servizio di facciata (se necessario). Un servizio di facciata è utile quando una singola richiesta deve interagire con più servizi o quando si vuole nascondere la complessità del sistema sottostante dal client. Tuttavia, se il servizio disaccoppiato non dispone di API pubbliche, potrebbe non essere necessario un servizio di facciata.

    Nella codebase dell'app Web monolitica implementare un servizio di facciata per instradare le richieste al back-end appropriato (monolith o microservizio). Assicurarsi che il nuovo servizio disaccoppiato possa gestire le richieste in modo indipendente quando si accede tramite la facciata.

Implementare il modello di livellamento del carico basato su coda

Implementare il modello di livellamento del carico basato su coda nella parte producer del servizio disaccoppiato per gestire in modo asincrono le attività che non necessitano di risposte immediate. Questo modello migliora la velocità di risposta e la scalabilità complessive del sistema usando una coda per gestire la distribuzione del carico di lavoro. Consente al servizio disaccoppiato di elaborare le richieste a una velocità coerente. Per implementare questo modello in modo efficace, attenersi alle indicazioni seguenti:

  • Usare l'accodamento messaggi non bloccante. Assicurarsi che il processo che invia messaggi alla coda non blocchi altri processi mentre attende che il servizio disaccoppiato gestisca i messaggi nella coda. Se il processo richiede il risultato dell'operazione di disaccoppiata del servizio, implementare un modo alternativo per gestire la situazione durante l'attesa del completamento dell'operazione in coda. Ad esempio, in Spring Boot è possibile usare la classe StreamBridge per pubblicare in modo asincrono i messaggi nella coda senza bloccare il thread chiamante:

    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");
    }
    

    Questo esempio Java usa StreamBridge per inviare messaggi in modo asincrono. Questo approccio garantisce che l'applicazione principale rimanga reattiva e possa gestire contemporaneamente altre attività mentre il servizio disaccoppiato elabora le richieste in coda a una velocità gestibile.

  • Implementare la ripetizione e la rimozione dei messaggi. Implementare un meccanismo per ritentare l'elaborazione dei messaggi in coda che non possono essere elaborati correttamente. Se gli errori vengono mantenuti, questi messaggi devono essere rimossi dalla coda. Ad esempio, bus di servizio include funzionalità predefinite per la coda di tentativi e messaggi non recapitabili.

  • Configurare l'elaborazione dei messaggi idempotenti. La logica che elabora i messaggi dalla coda deve essere idempotente per gestire i casi in cui un messaggio potrebbe essere elaborato più volte. In Spring Boot è possibile usare @StreamListener o @KafkaListener con un identificatore di messaggio univoco per impedire l'elaborazione duplicata. In alternativa, è possibile organizzare il processo aziendale per operare in un approccio funzionale con Spring Cloud Stream, in cui il consume metodo viene definito in modo da produrre lo stesso risultato quando viene eseguito ripetutamente. Per un elenco delle impostazioni che gestiscono il comportamento dell'utilizzo dei messaggi, vedere Spring Cloud Stream con il bus di servizio.

  • Gestire le modifiche apportate all'esperienza utente. Quando si usa l'elaborazione asincrona, le attività potrebbero non essere completate immediatamente. Per impostare le aspettative ed evitare confusione, assicurarsi che gli utenti sappiano quando le attività sono ancora in corso di elaborazione. Usare segnali visivi o messaggi per indicare che un'attività è in corso. Consentire agli utenti di ricevere notifiche al termine dell'attività, ad esempio un messaggio di posta elettronica o una notifica push.

Implementare il modello Consumer concorrenti

Implementare il modello Consumer concorrenti nel servizio disaccoppiato per gestire le attività in ingresso dalla coda di messaggi. Questo modello comporta la distribuzione di attività tra più istanze di servizi disaccoppiati. Questi servizi elaborano i messaggi dalla coda. Il modello migliora il bilanciamento del carico e aumenta la capacità del sistema per la gestione delle richieste simultanee. Il modello Consumer concorrenti è efficace quando:

  • La sequenza di elaborazione dei messaggi non è fondamentale.
  • La coda rimane inalterata dai messaggi in formato non valido.
  • L'operazione di elaborazione è idempotente, ovvero può essere applicata più volte senza modificare il risultato dopo l'applicazione iniziale.

Per implementare il modello Consumer concorrenti, seguire queste raccomandazioni:

  • Gestire messaggi simultanei. Quando i servizi ricevono messaggi da una coda, assicurarsi che il sistema venga ridimensionato in modo prevedibile configurando la concorrenza in modo che corrisponda alla progettazione del sistema. I risultati del test di carico consentono di determinare il numero appropriato di messaggi simultanei da gestire. È possibile iniziare da una per misurare le prestazioni del sistema.

  • Disabilitare il prelettura. Disabilitare il prelettura dei messaggi in modo che i consumer recuperino solo i messaggi quando sono pronti.

  • Usare modalità di elaborazione dei messaggi affidabili. Usare una modalità di elaborazione affidabile, ad esempio Peek-Lock, che ritenta automaticamente i messaggi che non riescono a elaborare. Questa modalità offre maggiore affidabilità rispetto ai metodi di eliminazione.this mode provides more reliability than deletion-first methods. Se un ruolo di lavoro non riesce a gestire un messaggio, un altro deve essere in grado di elaborarlo senza errori, anche se il messaggio viene elaborato più volte.

  • Implementare la gestione degli errori. Instradare messaggi non elaborati o non elaborabili a una coda di messaggi non recapitabili separata. Questa progettazione impedisce l'elaborazione ripetitiva. Ad esempio, è possibile intercettare le eccezioni durante l'elaborazione dei messaggi e spostare i messaggi problematici nella coda separata. Con il bus di servizio, i messaggi vengono spostati nella coda dei messaggi non recapitabili dopo un numero specificato di tentativi di recapito o in caso di rifiuto esplicito da parte dell'applicazione.

  • Gestire i messaggi non in ordine. Progettare i consumer per elaborare i messaggi che arrivano fuori sequenza. Quando si dispone di più consumer paralleli, possono elaborare i messaggi non in ordine.

  • Scala in base alla lunghezza della coda. I servizi che utilizzano messaggi da una coda devono essere ridimensionati automaticamente in base alla lunghezza della coda. La scalabilità automatica basata su scalabilità consente un'elaborazione efficiente dei picchi di messaggi in arrivo.

  • Usare una coda message-reply. Se il sistema richiede notifiche per l'elaborazione post-messaggio, configurare una coda di risposta o risposta dedicata. Questa configurazione separa la messaggistica operativa dai processi di notifica.

  • Usare i servizi senza stato. Prendere in considerazione l'uso di servizi senza stato per elaborare le richieste da una coda. In questo modo è possibile ridimensionare e usare le risorse in modo semplice ed efficiente.

  • Configurare la registrazione. Integrare la registrazione e la gestione delle eccezioni specifiche all'interno del flusso di lavoro di elaborazione dei messaggi. Concentrarsi sull'acquisizione degli errori di serializzazione e sull'indirizzamento di questi messaggi problematici a un meccanismo di messaggi non recapitabili. Questi log forniscono informazioni utili per la risoluzione dei problemi.

L'implementazione di riferimento usa il modello Consumer concorrenti in un servizio senza stato eseguito in App contenitore per elaborare le richieste di recapito tramite posta elettronica da una coda del bus di servizio.

Il processore registra i dettagli di elaborazione dei messaggi per facilitare la risoluzione dei problemi e il monitoraggio. Acquisisce gli errori di deserializzazione e fornisce informazioni dettagliate utili durante il debug. Il servizio viene ridimensionato a livello di contenitore per consentire una gestione efficiente dei picchi di messaggi in base alla lunghezza della coda. Ecco il codice:

@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);
            }
        };
    }
}

Implementare il modello di monitoraggio degli endpoint di integrità

Implementare il modello di monitoraggio degli endpoint di integrità nel codice dell'app principale e nel codice del servizio disaccoppiato per tenere traccia dell'integrità degli endpoint dell'applicazione. Gli agenti di orchestrazione come il servizio Azure Kubernetes o le app contenitore possono eseguire il polling di questi endpoint per verificare l'integrità del servizio e riavviare le istanze non integre. Spring Boot Azionatore offre il supporto predefinito per i controlli di integrità. Può esporre endpoint di controllo dell'integrità per le dipendenze chiave, ad esempio database, broker messaggi e sistemi di archiviazione. Per implementare il modello di monitoraggio degli endpoint di integrità, attenersi alle indicazioni seguenti:

  • Implementare i controlli di integrità. Usare Spring Boot Per implementare endpoint di controllo dell'integrità. L'attuatore espone un endpoint /actuator/health che include indicatori di integrità predefiniti e controlli personalizzati per varie dipendenze. Per abilitare l'endpoint di integrità, aggiungere la dipendenza spring-boot-starter-actuator nel file pom.xml o build.gradle:

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

    Configurare l'endpoint di integrità in application.properties come illustrato nell'implementazione di riferimento:

        management.endpoints.web.exposure.include=metrics,health,info,retry,retryevents
    
  • Convalidare le dipendenze. Spring Boot Agent include indicatori di integrità per varie dipendenze, ad esempio database, broker di messaggi (RabbitMQ o Kafka) e servizi di archiviazione. Per convalidare la disponibilità dei servizi di Azure, ad esempio Archiviazione BLOB di Azure o bus di servizio, usare tecnologie come Azure Spring Apps o Micrometer, che forniscono indicatori di integrità per questi servizi. Se sono necessari controlli personalizzati, è possibile implementarli creando un HealthIndicator bean personalizzato:

    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.
        }
    }
    
  • Configurare le risorse di Azure. Configurare la risorsa di Azure per usare gli URL di controllo integrità dell'app per confermare la disponibilità e la conformità. Ad esempio, è possibile usare Terraform per verificare la disponibilità e l'idoneità delle app distribuite nelle app contenitore. Per altre informazioni, vedere Probe di integrità in App contenitore.

Implementare il modello di ripetizione dei tentativi

Il modello di ripetizione dei tentativi consente alle applicazioni di eseguire il ripristino da errori temporanei. Questo modello è fondamentale per il modello Reliable Web App, quindi l'app Web deve già usare il modello Di ripetizione dei tentativi. Applicare il modello Di ripetizione dei tentativi alle richieste ai sistemi di messaggistica e alle richieste rilasciate dai servizi disaccoppiati estratti dall'app Web. Per implementare il modello Di ripetizione dei tentativi, seguire queste indicazioni:

  • Configurare le opzioni di ripetizione dei tentativi. Assicurarsi di configurare il client responsabile delle interazioni con la coda di messaggi con le impostazioni di ripetizione appropriate. Specificare parametri come il numero massimo di tentativi, ritardo tra i tentativi e il ritardo massimo.

  • Usare il backoff esponenziale. Implementare la strategia di backoff esponenziale per i tentativi. Questa strategia comporta l'aumento del tempo tra ogni tentativo in modo esponenziale, che consente di ridurre il carico nel sistema durante periodi di elevati tassi di errore.

  • Usare la funzionalità di ripetizione dei tentativi dell'SDK. Per i servizi con SDK specializzati, ad esempio il bus di servizio o l'archiviazione BLOB, usare i meccanismi di ripetizione dei tentativi predefiniti. Questi meccanismi predefiniti sono ottimizzati per i casi d'uso tipici del servizio, possono gestire i tentativi in modo più efficace e richiedere meno configurazione.

  • Usare librerie di resilienza standard per i client HTTP. Per i client HTTP, è possibile usare Resilience4j insieme a RestTemplate o WebClient di Spring per gestire i tentativi nelle comunicazioni HTTP. È possibile eseguire il wrapping di RestTemplate con la logica di ripetizione dei tentativi di Resilience4j per gestire in modo efficace gli errori HTTP temporanei.

  • Gestire il blocco dei messaggi. Per i sistemi basati su messaggi, implementare strategie di gestione dei messaggi che supportano i tentativi senza perdita di dati. Ad esempio, usare le modalità peek-lock quando sono disponibili. Assicurarsi che i messaggi non riusciti vengano ritentati in modo efficace e spostati in una coda di messaggi non recapitabili dopo errori ripetuti.

Linee guida per la configurazione

Le sezioni seguenti forniscono indicazioni per l'implementazione degli aggiornamenti della configurazione. Ogni sezione è allineata a uno o più dei pilastri del framework di Well-Architected.

Impostazione Affidabilità (RE) Sicurezza (SE) Ottimizzazione costi (CO) Eccellenza operativa (OE) Efficienza delle prestazioni (PE) Supporto dei principi del framework ben progettato
Configurare l'autenticazione e l'autorizzazione SE:05
OE:10
Implementare la scalabilità automatica indipendente RE:06
CO:12
PE:05
Distribuire un servizio in contenitori CO:13
PE:09
PE:03

Configurare l'autenticazione e l'autorizzazione

Per configurare l'autenticazione e l'autorizzazione in tutti i nuovi servizi di Azure (identità del carico di lavoro) aggiunti all'app Web, seguire queste indicazioni:

  • Usare le identità gestite per ogni nuovo servizio. Ogni servizio indipendente deve avere una propria identità e usare le identità gestite per l'autenticazione da servizio a servizio. Le identità gestite eliminano la necessità di gestire le credenziali nel codice e ridurre il rischio di perdita di credenziali. Consentono di evitare di includere informazioni riservate come le stringhe di connessione nel codice o nei file di configurazione.

  • Concedere privilegi minimi a ogni nuovo servizio. Assegnare solo le autorizzazioni necessarie a ogni nuova identità del servizio. Ad esempio, se un'identità deve eseguire solo il push in un registro contenitori, non concedere autorizzazioni pull. Esaminare regolarmente queste autorizzazioni e modificarle in base alle esigenze. Usare identità diverse per ruoli diversi, ad esempio la distribuzione e l'applicazione. In questo modo si limita il potenziale danno se un'identità viene compromessa.

  • Usare l'infrastruttura come codice (IaC). Usare Bicep o uno strumento IaC simile come Terraform per definire e gestire le risorse cloud. IaC garantisce un'applicazione coerente delle configurazioni di sicurezza nelle distribuzioni e consente di controllare la versione della configurazione dell'infrastruttura.

Per configurare l'autenticazione e l'autorizzazione per gli utenti (identità utente), seguire queste indicazioni:

  • Concedere privilegi minimi agli utenti. Come per i servizi, assicurarsi che gli utenti dispongano solo delle autorizzazioni necessarie per eseguire le attività. Esaminare e modificare regolarmente queste autorizzazioni.

  • Eseguire controlli di sicurezza regolari. Esaminare e controllare regolarmente la configurazione della sicurezza. Cercare errori di configurazione e autorizzazioni non necessarie e rettificarle o rimuoverle immediatamente.

L'implementazione di riferimento usa IaC per assegnare identità gestite a servizi aggiunti e ruoli specifici a ogni identità. Definisce i ruoli e l'accesso alle autorizzazioni per la distribuzione definendo i ruoli per i push e il pull del Registro Container. Ecco il codice:

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
}

Configurare la scalabilità automatica indipendente

Il modello di app Web moderna inizia a suddividere l'architettura monolitica e introduce il disaccoppiamento dei servizi. Quando si separa un'architettura di app Web, è possibile ridimensionare i servizi disaccoppiati in modo indipendente. Il ridimensionamento dei servizi di Azure per supportare un servizio app Web indipendente, anziché un'intera app Web, ottimizza i costi di ridimensionamento in base alle esigenze. Per ridimensionare automaticamente i contenitori, seguire queste indicazioni:

  • Usare i servizi senza stato. Assicurarsi che i servizi siano senza stato. Se l'app Web contiene lo stato della sessione in-process, esternalizzarla a una cache distribuita come Redis o a un database come SQL Server.

  • Configurare le regole di scalabilità automatica. Usare le configurazioni di scalabilità automatica che forniscono il controllo più conveniente sui servizi. Per i servizi in contenitori, il ridimensionamento basato su eventi, ad esempio Kubernetes Event-Driven scalabilità automatica (KEDA), offre spesso un controllo granulare che consente di ridimensionare in base alle metriche degli eventi. Le app contenitore e il servizio Azure Kubernetes supportano KEDA. Per i servizi che non supportano KEDA, ad esempio servizio app, usare le funzionalità di scalabilità automatica fornite dalla piattaforma stessa. Queste funzionalità includono spesso il ridimensionamento in base alle regole basate sulle metriche o sul traffico HTTP.

  • Configurare le repliche minime. Per evitare l'avvio a freddo, configurare le impostazioni di scalabilità automatica per mantenere almeno una replica. Un avvio a freddo è l'inizializzazione di un servizio da uno stato arrestato. Un avvio a freddo spesso ritarda la risposta. Se ridurre al minimo i costi è una priorità ed è possibile tollerare ritardi di avvio a freddo, impostare il numero minimo di repliche su 0 quando si configura la scalabilità automatica.

  • Configurare un periodo di raffreddamento. Applicare un periodo di raffreddamento appropriato per introdurre un ritardo tra gli eventi di ridimensionamento. L'obiettivo è prevenire attività di ridimensionamento eccessive attivate da picchi di carico temporanei.

  • Configurare il ridimensionamento basato su coda. Se l'applicazione usa una coda di messaggi come il bus di servizio, configurare le impostazioni di scalabilità automatica in base alla lunghezza della coda dei messaggi di richiesta. Il scaler tenta di mantenere una replica del servizio per ogni N messaggi nella coda (arrotondati).

Ad esempio, l'implementazione di riferimento usa il scaler KEDA bus di servizio per ridimensionare automaticamente l'app contenitore in base alla lunghezza della coda bus di servizio. La regola di ridimensionamento, denominata service-bus-queue-length-rule, regola il numero di repliche del servizio in base al numero di messaggi nella coda del bus di servizio specificata. Il parametro messageCount è impostato su 10, che configura il scaler per aggiungere una replica per ogni 10 messaggi nella coda. Il numero massimo di repliche (max_replicas) è impostato su 10. Il numero minimo di repliche è implicitamente 0 a meno che non venga sottoposto a override. Questa configurazione consente al servizio di ridurre fino a zero quando non sono presenti messaggi nella coda. La stringa di connessione per la coda del bus di servizio viene archiviata come segreto in Azure, denominata azure-servicebus-connection-string, che viene usata per autenticare il ridimensionatore nel bus di servizio. Ecco il codice 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"
      }
    }

Distribuire un servizio in contenitori

La containerizzazione è l'incapsulamento di tutte le dipendenze necessarie per l'app in un'immagine leggera che può essere distribuita in modo affidabile in un'ampia gamma di host. Per inserire in contenitori la distribuzione, seguire queste indicazioni:

  • Identificare i limiti del dominio. Per iniziare, identificare i limiti di dominio nell'applicazione monolitica. In questo modo è possibile determinare quali parti dell'applicazione è possibile estrarre in servizi separati.

  • Creare immagini Docker. Quando si creano immagini Docker per i servizi Java, usare immagini di base OpenJDK ufficiali. Queste immagini contengono solo il set minimo di pacchetti che Java deve eseguire. L'uso di queste immagini riduce al minimo le dimensioni del pacchetto e la superficie di attacco.

  • Usare Dockerfile a più fasi. Usare un Dockerfile a più fasi per separare gli asset in fase di compilazione dall'immagine del contenitore di runtime. L'uso di questo tipo di file consente di mantenere le immagini di produzione piccole e sicure. È anche possibile usare un server di compilazione preconfigurato e copiare il file JAR nell'immagine del contenitore.

  • Eseguire come utente nonroot. Eseguire i contenitori Java come utente nonroot (tramite nome utente o UID $APP_UID) per allinearsi al principio dei privilegi minimi. In questo modo è possibile limitare gli effetti potenziali di un contenitore compromesso.

  • Ascolto sulla porta 8080. Quando si eseguono contenitori come utente nonroot, configurare l'applicazione per l'ascolto sulla porta 8080. Si tratta di una convenzione comune per gli utenti nonroot.

  • Incapsulare le dipendenze. Assicurarsi che tutte le dipendenze necessarie per l'app siano incapsulate nell'immagine del contenitore Docker. L'incapsulamento consente di distribuire l'app in modo affidabile in un'ampia gamma di host.

  • Scegliere le immagini di base appropriate. L'immagine di base scelta dipende dall'ambiente di distribuzione. Se si esegue la distribuzione in App contenitore, ad esempio, è necessario usare immagini Docker Linux.

L'implementazione di riferimento illustra un processo di compilazione Docker per la containerizzazione di un'applicazione Java. Il Dockerfile usa una compilazione a fase singola con l'immagine di base OpenJDK (mcr.microsoft.com/openjdk/jdk:17-ubuntu), che fornisce l'ambiente di runtime Java necessario.

Il Dockerfile include i passaggi seguenti:

  1. Dichiarazione del volume. Viene definito un volume temporaneo (/tmp). Questo volume fornisce un archivio file temporaneo separato dal file system principale del contenitore.
  2. Copia degli artefatti. Il file JAR dell'applicazione (email-processor.jar) viene copiato nel contenitore, insieme all'agente di Application Insights (applicationinsights-agent.jar) usato per il monitoraggio.
  3. Impostazione del punto di ingresso. Il contenitore è configurato per eseguire l'applicazione con l'agente di Application Insights abilitato. Il codice usa java -javaagent per monitorare l'applicazione durante il runtime.

Il Dockerfile mantiene l'immagine piccola includendo solo le dipendenze di runtime. È adatto per ambienti di distribuzione come App contenitore che supportano contenitori basati su 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"]

Distribuire l'implementazione di riferimento

Distribuire l'implementazione di riferimento del modello di app Web moderno per Java. Nel repository sono disponibili istruzioni sia per lo sviluppo che per la distribuzione di produzione. Dopo aver distribuito l'implementazione, è possibile simulare e osservare i modelli di progettazione.

Il diagramma seguente illustra l'architettura dell'implementazione di riferimento:

Diagramma che mostra l'architettura dell'implementazione di riferimento.

Scaricare un file di Visio di questa architettura.