Bewerken

Delen via


Modern Web App-patroon voor Java

Azure App Service
Azure Service Bus

In dit artikel wordt beschreven hoe u het patroon moderne web-apps implementeert. Het patroon Moderne web-app definieert hoe u cloudweb-apps moderniseert en een servicegerichte architectuur introduceert. Het patroon biedt prescriptieve architectuur, code en configuratierichtlijnen die zijn afgestemd op de principes van de Azure Well-Architected Framework. Dit patroon is gebaseerd op het Reliable Web App-patroon.

Waarom het patroon Moderne web-app gebruiken?

Met het patroon Moderne web-app kunt u gebieden met hoge vraag van uw webtoepassing optimaliseren. Het biedt gedetailleerde richtlijnen voor het loskoppelen van deze gebieden om onafhankelijk schalen voor kostenoptimalisatie mogelijk te maken. Met deze aanpak kunt u toegewezen resources toewijzen aan kritieke onderdelen, waardoor de algehele prestaties worden verbeterd. Het loskoppelen van afzonderlijke services kan de betrouwbaarheid verbeteren door vertragingen in één deel van de app te voorkomen. Het maakt ook onafhankelijke versiebeheer van afzonderlijke app-onderdelen mogelijk.

Het patroon Moderne web-app implementeren

Dit artikel bevat richtlijnen voor het implementeren van het patroon Moderne web-app. Gebruik de volgende koppelingen om naar de specifieke richtlijnen te gaan die u nodig hebt:

  • richtlijnen voor architectuur. Leer hoe u onderdelen van web-apps modulariseert en de juiste PaaS-oplossingen (Platform as a Service) selecteert.
  • coderichtlijnen. Implementeer vier ontwerppatronen om de losgekoppelde onderdelen te optimaliseren: Strangler Fig, Queue-Based Load Leveling, Concurrerende consumenten en Statuseindpuntbewaking.
  • configuratierichtlijnen. Configureer verificatie, autorisatie, automatisch schalen en containerisatie voor de losgekoppelde onderdelen.

Tip

GitHub-logo Er is een referentie-implementatie (voorbeeld-app) van het patroon Moderne web-app. Het vertegenwoordigt de eindstatus van de implementatie van de moderne web-app. Het is een web-app op productieniveau die alle code-, architectuur- en configuratie-updates bevat die in dit artikel worden besproken. Implementeer en gebruik de referentie-implementatie om uw implementatie van het moderne web-app-patroon te begeleiden.

Uitleg bij architectuur

Het patroon Moderne web-app is gebaseerd op het patroon Reliable Web App. Hiervoor zijn enkele extra architectuuronderdelen vereist. U hebt een berichtenwachtrij, containerplatform, opslagservice en containerregister nodig, zoals wordt weergegeven in het volgende diagram:

diagram met de basislijnarchitectuur van het patroon Moderne web-app.

Voor een hogere serviceniveaudoelstelling (SLO) kunt u een tweede regio toevoegen aan uw web-app-architectuur. Configureer uw load balancer om verkeer naar de tweede regio te routeren ter ondersteuning van een actief-actief- of actief-passieve configuratie, afhankelijk van uw bedrijfsbehoeften. Voor de twee regio's zijn dezelfde services vereist, behalve dat één regio een virtueel hubnetwerk heeft. Gebruik een hub-and-spoke-netwerktopologie om resources, zoals een netwerkfirewall, te centraliseren en te delen. Open de containeropslagplaats via het virtuele hubnetwerk. Als u virtuele machines hebt, voegt u een bastionhost toe aan het virtuele hubnetwerk om deze te beheren met verbeterde beveiliging. In het volgende diagram ziet u deze architectuur:

diagram met de architectuur van het moderne web-app-patroon met een tweede regio.

De architectuur loskoppelen

Als u het patroon Moderne web-app wilt implementeren, moet u de bestaande web-app-architectuur loskoppelen. Het loskoppelen van de architectuur omvat het opsplitsen van een monolithische toepassing in kleinere, onafhankelijke services, die elk verantwoordelijk zijn voor een specifieke functie of functie. Dit proces omvat het evalueren van de huidige web-app, het wijzigen van de architectuur en het extraheren van de code van de web-app naar een containerplatform. Het doel is om toepassingsservices die het meeste profiteren van ontkoppeld, systematisch te identificeren en te extraheren. Volg deze aanbevelingen om uw architectuur los te koppelen:

  • Servicegrenzen identificeren. Pas ontwerpprincipes op basis van een domein toe om gebonden contexten binnen uw monolithische toepassing te identificeren. Elke gebonden context vertegenwoordigt een logische grens en is een kandidaat voor ontkoppeling. Services die afzonderlijke bedrijfsfuncties vertegenwoordigen en minder afhankelijkheden hebben, zijn goede kandidaten.

  • Evalueer servicevoordelen. Richt u op services die het meest profiteren van onafhankelijk schalen. Een externe afhankelijkheid, zoals een e-mailserviceprovider in een LOB-toepassing, vereist bijvoorbeeld mogelijk meer isolatie van fouten. Overweeg services die regelmatig updates of wijzigingen ondergaan. Het loskoppelen van deze services maakt onafhankelijke implementatie mogelijk en vermindert het risico dat andere onderdelen van de toepassing worden beïnvloed.

  • Technische haalbaarheid beoordelen. Bekijk de huidige architectuur om technische beperkingen en afhankelijkheden te identificeren die van invloed kunnen zijn op het ontkoppelingsproces. Plan hoe u gegevens in services beheert en deelt. Losgekoppelde services moeten hun eigen gegevens beheren en directe databasetoegang over servicegrenzen minimaliseren.

  • Azure-services implementeren. Selecteer en implementeer de Azure-services die u nodig hebt om de web-app-service te ondersteunen die u wilt extraheren. Zie de sectie De juiste Azure-services selecteren sectie van dit artikel voor hulp.

  • Koppel de web-app-service los. Definieer duidelijke interfaces en API's die de zojuist geëxtraheerde web-app-services kunnen gebruiken om te communiceren met andere onderdelen van het systeem. Ontwerp een strategie voor gegevensbeheer waarmee elke service zijn eigen gegevens kan beheren, maar zorgt voor consistentie en integriteit. Zie de sectie Code-richtlijnen voor specifieke implementatiestrategieën en ontwerppatronen die tijdens dit extractieproces moeten worden gebruikt.

  • Gebruik onafhankelijke opslag voor losgekoppelde services. Om versiebeheer en implementatie te vereenvoudigen, moet u ervoor zorgen dat elke losgekoppelde service zijn eigen gegevensarchieven heeft. De referentie-implementatie scheidt bijvoorbeeld de e-mailservice van de web-app en elimineert de noodzaak voor de service om toegang te krijgen tot de database. In plaats daarvan communiceert de service de status van de e-mailbezorging terug naar de web-app via een Azure Service Bus-bericht en slaat de web-app een notitie op in de database.

  • Implementeer afzonderlijke implementatiepijplijnen voor elke losgekoppelde service. Als u afzonderlijke implementatiepijplijnen implementeert, kan elke service volgens een eigen planning worden bijgewerkt. Als verschillende teams of organisaties binnen uw bedrijf eigenaar zijn van verschillende services, biedt het gebruik van afzonderlijke implementatiepijplijnen elk team controle over zijn eigen implementaties. Gebruik hulpprogramma's voor continue integratie en continue levering (CI/CD), zoals Jenkins, GitHub Actions of Azure Pipelines om deze pijplijnen in te stellen.

  • Besturingselementen voor beveiliging herzien. Zorg ervoor dat uw beveiligingscontroles worden bijgewerkt om rekening te houden met de nieuwe architectuur, waaronder firewallregels en toegangsbeheer.

De juiste Azure-services selecteren

Raadpleeg voor elke Azure-service in uw architectuur de relevante Azure-servicehandleiding in het Well-Architected Framework. Voor het patroon Moderne web-app hebt u een berichtensysteem nodig om asynchrone berichten te ondersteunen, een toepassingsplatform dat ondersteuning biedt voor containerisatie en een opslagplaats voor containerinstallatiekopieën.

  • Kies een berichtenwachtrij. Een berichtenwachtrij is een belangrijk onderdeel van servicegerichte architecturen. Hiermee worden afzenders en ontvangers van berichten ontkoppeld om asynchrone berichten in te schakelen. Gebruik de richtlijnen voor het kiezen van een Azure Messaging-service om een Azure Messaging-systeem te kiezen dat uw ontwerpbehoeften ondersteunt. Azure heeft drie berichtenservices: Azure Event Grid, Azure Event Hubs en Service Bus. Begin met Service Bus en gebruik een van de andere twee opties als Service Bus niet aan uw behoeften voldoet.

    Service Gebruiksscenario
    Service Bus Kies Service Bus voor betrouwbare, geordende en mogelijk transactionele levering van hoogwaardige berichten in bedrijfstoepassingen.
    Event Grid Kies Event Grid wanneer u een groot aantal discrete gebeurtenissen efficiënt moet afhandelen. Event Grid is schaalbaar voor gebeurtenisgestuurde toepassingen waarin veel kleine, onafhankelijke gebeurtenissen (zoals wijzigingen in de resourcestatus) moeten worden doorgestuurd naar abonnees in een publicatie-abonnementsmodel met lage latentie.
    Event Hubs Kies Event Hubs voor enorme gegevensopname met hoge doorvoer, zoals telemetrie, logboeken of realtime analyses. Event Hubs is geoptimaliseerd voor streamingscenario's waarin bulkgegevens continu moeten worden opgenomen en verwerkt.
  • Een containerservice implementeren. Voor de elementen van uw toepassing die u wilt containeriseren, hebt u een toepassingsplatform nodig dat containers ondersteunt. De Een Azure-containerservice kiezen richtlijnen kunnen u helpen bij het selecteren van een containerservice. Azure heeft drie principal-containerservices: Azure Container Apps, Azure Kubernetes Service (AKS) en Azure-app Service. Begin met Container Apps en gebruik een van de andere twee opties als Container Apps niet aan uw behoeften voldoet.

    Service Gebruiksscenario
    Container Apps Kies Container Apps als u een serverloos platform nodig hebt dat containers automatisch schaalt en beheert in gebeurtenisgestuurde toepassingen.
    AKS Kies AKS als u gedetailleerde controle nodig hebt over Kubernetes-configuraties en geavanceerde functies voor schalen, netwerken en beveiliging.
    Web App for Containers Kies Web App for Containers in App Service voor de eenvoudigste PaaS-ervaring.
  • Implementeer een containeropslagplaats. Wanneer u een rekenservice op basis van een container gebruikt, moet u een opslagplaats hebben om de containerinstallatiekopieën op te slaan. U kunt een openbaar containerregister gebruiken, zoals Docker Hub of een beheerd register, zoals Azure Container Registry. De Inleiding tot containerregisters in Azure richtlijnen kunnen u helpen er een te kiezen.

Richtlijnen voor code

Als u een onafhankelijke service wilt loskoppelen en extraheren, moet u de code van uw web-app bijwerken met de volgende ontwerppatronen: Strangler Fig, Queue-Based load leveling, concurrerende consumenten, statuseindpuntbewaking en opnieuw proberen. In het volgende diagram ziet u de rollen van deze patronen:

diagram met de rol van de ontwerppatronen in de architectuur van het moderne web-app-patroon.

  1. Strangler Fig-patroon: Het Strangler Fig-patroon migreert incrementeel functionaliteit van een monolithische toepassing naar de losgekoppelde service. Implementeer dit patroon in de hoofdweb-app om functionaliteit geleidelijk te migreren naar onafhankelijke services door verkeer te leiden op basis van eindpunten.

  2. Patroon load leveling op basis van wachtrij: het patroon Load Leveling op basis van wachtrij beheert de stroom van berichten tussen de producent en de consument door een wachtrij als buffer te gebruiken. Implementeer dit patroon op het producentgedeelte van de ontkoppelde service om de berichtstroom asynchroon te beheren met behulp van een wachtrij.

  3. Patroon concurrerende consumenten: met het patroon Concurrerende consumenten kunnen meerdere exemplaren van een ontkoppelde service onafhankelijk van dezelfde berichtenwachtrij lezen en concurreren om berichten te verwerken. Implementeer dit patroon in de losgekoppelde service om taken over meerdere exemplaren te distribueren.

  4. patroon Statuseindpuntbewaking: het patroon Statuseindpuntbewaking maakt eindpunten beschikbaar voor het bewaken van de status en status van verschillende onderdelen van de web-app. (4a) Implementeer dit patroon in de hoofdweb-app. (4b) Implementeer deze ook in de losgekoppelde service om de status van eindpunten bij te houden.

  5. Patroon voor opnieuw proberen: het patroon Opnieuw proberen verwerkt tijdelijke fouten door bewerkingen opnieuw uit te voeren die af en toe kunnen mislukken. (5a) Implementeer dit patroon in de hoofdweb-app, bij alle uitgaande aanroepen naar andere Azure-services, zoals aanroepen naar de berichtenwachtrij en privé-eindpunten. (5b) Implementeer dit patroon ook in de ontkoppelde service om tijdelijke fouten in aanroepen naar de privé-eindpunten af te handelen.

Elk ontwerppatroon biedt voordelen die zijn afgestemd op een of meer van de pijlers van het Well-Architected Framework. De volgende tabel bevat details.

Ontwerppatroon Implementatielocatie Betrouwbaarheid (RE) Beveiliging (SE) Kostenoptimalisatie (CO) Operational Excellence (OE) Prestatie-efficiëntie (PE) Goed ontworpen frameworkprincipes ondersteunen
Strangler Fig-patroon Hoofdweb-app RE:08
CO:07
CO:08
OE:06
OE:11
Patroon Load Leveling op basis van wachtrij Producent van losgekoppelde service RE:06
RE:07
CO:12
PE:05
Patroon concurrerende consumenten Losgekoppelde service RE:05
RE:07
CO:05
CO:07
PE:05
PE:07
Patroon Statuseindpuntbewaking Hoofdweb-app en losgekoppelde service RE:07
RE:10
OE:07
PE:05
Patroon opnieuw proberen Hoofdweb-app en losgekoppelde service RE:07

Het strangler Fig-patroon implementeren

Gebruik het patroon Strangler Fig om de functionaliteit geleidelijk te migreren van de monolithische codebasis naar nieuwe onafhankelijke services. Extraheer nieuwe services uit de bestaande monolithische codebasis en moderniseer langzaam kritieke onderdelen van de web-app. Volg deze aanbevelingen om het strangler Fig-patroon te implementeren:

  • Stel een routeringslaag in. Implementeer in de codebasis van de monolithische web-app een routeringslaag die verkeer omleidt op basis van eindpunten. Gebruik zo nodig aangepaste routeringslogica voor het afhandelen van specifieke bedrijfsregels voor het omleiden van verkeer. Als u bijvoorbeeld een /users eindpunt in uw monolithische app hebt en u die functionaliteit naar de ontkoppelde service verplaatst, worden alle aanvragen omgeleid naar /users naar de nieuwe service.

  • Implementatie van functies beheren.Implementeer functievlagmen en gefaseerde implementatie om de losgekoppelde services geleidelijk uit te rollen. De bestaande monolithische app-routering moet bepalen hoeveel aanvragen de losgekoppelde services ontvangen. Begin met een klein percentage aanvragen en verhoog het gebruik in de loop van de tijd naarmate u vertrouwen krijgt in de stabiliteit en prestaties van de service.

    De referentie-implementatie extraheert bijvoorbeeld de functionaliteit voor e-mailbezorging in een zelfstandige service. De service kan geleidelijk worden geïntroduceerd voor het verwerken van een groter percentage van de aanvragen voor het verzenden van e-mailberichten die Contoso-ondersteuningsgidsen bevatten. Omdat de nieuwe service de betrouwbaarheid en prestaties bewijst, kan deze uiteindelijk de volledige set e-mailverantwoordelijkheden van de monolithische gegevens overnemen, waardoor de overgang wordt voltooid.

  • Gebruik een gevelservice (indien nodig). Een gevelservice is handig wanneer één aanvraag moet communiceren met meerdere services of wanneer u de complexiteit van het onderliggende systeem van de client wilt verbergen. Als de ontkoppelde service echter geen openbare API's heeft, is een façadeservice mogelijk niet nodig.

    Implementeer in de codebasis van de monolithische web-app een façadeservice om aanvragen naar de juiste back-end (monolith of microservice) te routeren. Zorg ervoor dat de nieuwe losgekoppelde service aanvragen onafhankelijk kan verwerken wanneer deze toegankelijk is via de gevel.

Het patroon Load Leveling op basis van wachtrij implementeren

Implementeer het patroon load leveling op basis van wachtrijen op het producentgedeelte van de losgekoppelde service om taken die niet direct hoeven te worden beantwoord, asynchroon af te handelen. Dit patroon verbetert de algehele reactiesnelheid en schaalbaarheid van het systeem met behulp van een wachtrij voor het beheren van de workloaddistributie. Hiermee kan de ontkoppelde service aanvragen met een consistente snelheid verwerken. Volg deze aanbevelingen om dit patroon effectief te implementeren:

  • Gebruik niet-blokkerende berichtenwachtrijen. Zorg ervoor dat het proces waarmee berichten naar de wachtrij worden verzonden, geen andere processen blokkeert terwijl wordt gewacht totdat de losgekoppelde service berichten in de wachtrij verwerkt. Als voor het proces het resultaat van de losgekoppelde servicebewerking is vereist, implementeert u een alternatieve manier om de situatie af te handelen terwijl wordt gewacht tot de bewerking in de wachtrij is voltooid. In Spring Boot kunt u bijvoorbeeld de StreamBridge-klasse gebruiken om asynchroon berichten naar de wachtrij te publiceren zonder de aanroepende thread te blokkeren:

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

    In dit Java-voorbeeld worden StreamBridge berichten asynchroon verzonden. Deze aanpak zorgt ervoor dat de hoofdtoepassing responsief blijft en andere taken gelijktijdig kan verwerken terwijl de losgekoppelde service de aanvragen in de wachtrij met een beheerbaar tarief verwerkt.

  • Implementeer het opnieuw proberen en verwijderen van berichten. Implementeer een mechanisme voor het opnieuw verwerken van berichten in de wachtrij die niet kunnen worden verwerkt. Als er fouten optreden, moeten deze berichten uit de wachtrij worden verwijderd. Service Bus heeft bijvoorbeeld ingebouwde functies voor nieuwe pogingen en wachtrijen met dode letters.

  • Configureer idempotent berichtverwerking. De logica waarmee berichten uit de wachtrij worden verwerkt, moet idempotent zijn voor het afhandelen van gevallen waarin een bericht meerdere keren kan worden verwerkt. In Spring Boot kunt u of @StreamListener met een unieke bericht-id gebruiken @KafkaListener om dubbele verwerking te voorkomen. Of u kunt het bedrijfsproces organiseren om te werken in een functionele benadering met Spring Cloud Stream, waarbij de consume methode wordt gedefinieerd op een manier die hetzelfde resultaat produceert wanneer deze herhaaldelijk wordt uitgevoerd. Zie Spring Cloud Stream met Service Busvoor een lijst met instellingen waarmee het gedrag van het berichtverbruik wordt beheerd.

  • Wijzigingen in de gebruikerservaring beheren. Wanneer u asynchrone verwerking gebruikt, worden taken mogelijk niet onmiddellijk voltooid. Als u verwachtingen wilt instellen en verwarring wilt voorkomen, moet u ervoor zorgen dat gebruikers weten wanneer hun taken nog worden verwerkt. Gebruik visuele aanwijzingen of berichten om aan te geven dat een taak wordt uitgevoerd. Geef gebruikers de mogelijkheid om meldingen te ontvangen wanneer hun taak is voltooid, zoals een e-mail of pushmelding.

Het patroon Concurrerende consumenten implementeren

Implementeer het patroon Concurrerende consumenten in de ontkoppelde service om binnenkomende taken uit de berichtenwachtrij te beheren. Dit patroon omvat het distribueren van taken over meerdere instanties van losgekoppelde services. Deze services verwerken berichten uit de wachtrij. Het patroon verbetert de taakverdeling en verhoogt de capaciteit van het systeem voor het verwerken van gelijktijdige aanvragen. Het patroon Concurrerende consumenten is effectief wanneer:

  • De volgorde van berichtverwerking is niet van cruciaal belang.
  • De wachtrij blijft niet beïnvloed door verkeerd gevormde berichten.
  • De verwerkingsbewerking is idempotent, wat betekent dat deze meerdere keren kan worden toegepast zonder het resultaat na de eerste toepassing te wijzigen.

Volg deze aanbevelingen om het patroon Concurrerende consumenten te implementeren:

  • Gelijktijdige berichten verwerken. Wanneer services berichten ontvangen uit een wachtrij, moet u ervoor zorgen dat uw systeem voorspelbaar wordt geschaald door de gelijktijdigheid zo te configureren dat deze overeenkomt met het systeemontwerp. Met belastingtestresultaten kunt u het juiste aantal gelijktijdige berichten bepalen dat moet worden verwerkt. U kunt beginnen met één om te meten hoe het systeem presteert.

  • Schakel prefetching uit. Schakel het vooraf afhalen van berichten uit, zodat gebruikers alleen berichten ophalen wanneer ze klaar zijn.

  • Gebruik betrouwbare berichtverwerkingsmodi. Gebruik een betrouwbare verwerkingsmodus, zoals Peek-Lock, waarmee berichten die mislukken, automatisch opnieuw worden verwerkt. Deze modus biedt meer betrouwbaarheid dan verwijderings-eerste methoden. Als een werkrol een bericht niet kan verwerken, moet een andere werknemer het zonder fouten kunnen verwerken, zelfs als het bericht meerdere keren wordt verwerkt.

  • Foutafhandeling implementeren. Verkeerd gevormde of niet-verwerkte berichten doorsturen naar een afzonderlijke wachtrij met onbestelbare berichten. Dit ontwerp voorkomt terugkerende verwerking. U kunt bijvoorbeeld uitzonderingen vangen tijdens het verwerken van berichten en problematische berichten naar de afzonderlijke wachtrij verplaatsen. Met Service Bus worden berichten na een opgegeven aantal bezorgingspogingen of bij expliciete afwijzing door de toepassing verplaatst naar de wachtrij met dead-leter.

  • Berichten buiten de volgorde verwerken. Ontwerp consumenten om berichten te verwerken die niet op volgorde aankomen. Wanneer u meerdere parallelle consumenten hebt, kunnen ze berichten buiten de juiste volgorde verwerken.

  • Schalen op basis van de lengte van de wachtrij. Services die berichten uit een wachtrij gebruiken, moeten automatisch worden geschaald op basis van de lengte van de wachtrij. Automatisch schalen op basis van schaal zorgt voor een efficiënte verwerking van pieken in binnenkomende berichten.

  • Gebruik een berichtenwachtrij. Als uw systeem meldingen nodig heeft voor het verwerken van berichten, stelt u een speciale antwoord- of antwoordwachtrij in. Met deze installatie wordt operationele berichten gescheiden van meldingsprocessen.

  • Gebruik stateless services. Overweeg om stateless services te gebruiken om aanvragen uit een wachtrij te verwerken. Hierdoor is eenvoudig schalen en efficiënt resourcegebruik mogelijk.

  • Logboekregistratie configureren. Integreer logboekregistratie en specifieke uitzonderingsafhandeling in de werkstroom voor berichtverwerking. Richt u op het vastleggen van serialisatiefouten en het doorsturen van deze problematische berichten naar een mechanisme voor dode letters. Deze logboeken bieden waardevolle inzichten voor probleemoplossing.

De referentie-implementatie maakt gebruik van het patroon Concurrerende consumenten in een staatloze service die wordt uitgevoerd in Container Apps om aanvragen voor e-mailbezorging vanuit een Service Bus-wachtrij te verwerken.

De processor registreert berichtverwerkingsgegevens om te helpen bij het oplossen en bewaken van problemen. Hiermee worden deserialisatiefouten vastgelegd en worden inzichten geboden die nuttig kunnen zijn tijdens foutopsporing. De service wordt geschaald op containerniveau om een efficiënte verwerking van berichtpieken mogelijk te maken op basis van de wachtrijlengte. Dit is de code:

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

Het patroon Statuseindpuntbewaking implementeren

Implementeer het patroon Statuseindpuntbewaking in de hoofd-app-code en losgekoppelde servicecode om de status van toepassingseindpunten bij te houden. Orchestrators zoals AKS of Container Apps kunnen deze eindpunten peilen om de servicestatus te controleren en beschadigde exemplaren opnieuw op te starten. Spring Boot Actuator biedt ingebouwde ondersteuning voor statuscontroles. Het kan statuscontrole-eindpunten beschikbaar maken voor belangrijke afhankelijkheden, zoals databases, berichtenbrokers en opslagsystemen. Volg deze aanbevelingen om het patroon Statuseindpuntbewaking te implementeren:

  • Statuscontroles implementeren. Spring Boot Actuator gebruiken om statuscontrole-eindpunten te bieden. Actuator toont een /actuator/health-eindpunt met ingebouwde statusindicatoren en aangepaste controles voor verschillende afhankelijkheden. Als u het statuseindpunt wilt inschakelen, voegt u de spring-boot-starter-actuator afhankelijkheid toe aan uw pom.xml- of build.gradle-bestand:

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

    Configureer het statuseindpunt in application.properties zoals wordt weergegeven in de referentie-implementatie:

        management.endpoints.web.exposure.include=metrics,health,info,retry,retryevents
    
  • Afhankelijkheden valideren. Spring Boot Actuator bevat statusindicatoren voor verschillende afhankelijkheden zoals databases, berichtbrokers (RabbitMQ of Kafka) en opslagservices. Als u de beschikbaarheid van Azure-services, zoals Azure Blob Storage of Service Bus, wilt valideren, gebruikt u technologieën zoals Azure Spring Apps of Micrometer, die statusindicatoren bieden voor deze services. Als u aangepaste controles nodig hebt, kunt u deze implementeren door een aangepaste HealthIndicator an te maken:

    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.
        }
    }
    
  • Azure-resources configureren. Configureer de Azure-resource om de URL's van de statuscontrole van de app te gebruiken om de liveness en gereedheid te bevestigen. U kunt Bijvoorbeeld Terraform gebruiken om de liveheid en gereedheid van apps te bevestigen die zijn geïmplementeerd in Container Apps. Zie Statustests in Container Apps voor meer informatie.

Het patroon Opnieuw proberen implementeren

Met het patroon Voor opnieuw proberen kunnen toepassingen herstellen van tijdelijke fouten. Dit patroon is centraal in het patroon Reliable Web App, dus uw web-app moet al het patroon Opnieuw proberen gebruiken. Pas het patroon Opnieuw proberen toe op aanvragen op de berichtensystemen en aanvragen die worden uitgegeven door de losgekoppelde services die u uit de web-app haalt. Volg deze aanbevelingen om het patroon Opnieuw proberen te implementeren:

  • Configureer opties voor opnieuw proberen. Zorg ervoor dat u de client configureert die verantwoordelijk is voor interacties met de berichtenwachtrij met de juiste instellingen voor opnieuw proberen. Geef parameters op, zoals het maximum aantal nieuwe pogingen, vertraging tussen nieuwe pogingen en maximale vertraging.

  • Gebruik exponentieel uitstel. Implementeer de strategie voor exponentieel uitstel voor nieuwe pogingen. Deze strategie omvat het verhogen van de tijd tussen elke nieuwe poging exponentieel, wat helpt de belasting van het systeem te verminderen tijdens perioden van hoge foutpercentages.

  • Gebruik de sdk-functionaliteit voor opnieuw proberen. Voor services met gespecialiseerde SDK's, zoals Service Bus of Blob Storage, gebruikt u de ingebouwde mechanismen voor opnieuw proberen. Deze ingebouwde mechanismen zijn geoptimaliseerd voor de typische use cases van de service, kunnen nieuwe pogingen effectiever verwerken en vereisen minder configuratie.

  • Gebruik standaardtolerantiebibliotheken voor HTTP-clients. Voor HTTP-clients kunt u Resilience4j samen met Spring's RestTemplate of WebClient gebruiken om nieuwe pogingen in HTTP-communicatie af te handelen. U kunt RestTemplate verpakken met de logica voor opnieuw proberen van Resilience4j om tijdelijke HTTP-fouten effectief af te handelen.

  • Afhandelen van berichtvergrendeling. Voor systemen op basis van berichten implementeert u strategieën voor berichtafhandeling die ondersteuning bieden voor nieuwe pogingen zonder gegevensverlies. Gebruik bijvoorbeeld peek-lock-modi wanneer deze beschikbaar zijn. Zorg ervoor dat mislukte berichten effectief opnieuw worden geprobeerd en worden verplaatst naar een wachtrij met onbestelbare berichten na herhaalde fouten.

Configuratierichtlijnen

De volgende secties bevatten richtlijnen voor het implementeren van de configuratie-updates. Elke sectie wordt uitgelijnd met een of meer van de pijlers van het Well-Architected Framework.

Configuratie Betrouwbaarheid (RE) Beveiliging (SE) Kostenoptimalisatie (CO) Operational Excellence (OE) Prestatie-efficiëntie (PE) Goed ontworpen frameworkprincipes ondersteunen
Verificatie en autorisatie configureren SE:05
OE:10
Onafhankelijke automatische schaalaanpassing implementeren RE:06
CO:12
PE:05
Service-implementatie containeriseren CO:13
PE:09
PE:03

Verificatie en autorisatie configureren

Als u verificatie en autorisatie wilt configureren voor nieuwe Azure-services (workloadidentiteiten) die u aan de web-app toevoegt, volgt u deze aanbevelingen:

  • Gebruik beheerde identiteiten voor elke nieuwe service. Elke onafhankelijke service moet een eigen identiteit hebben en beheerde identiteiten gebruiken voor service-naar-service-verificatie. Beheerde identiteiten elimineren de noodzaak om referenties in uw code te beheren en het risico op het lekken van referenties te verminderen. Ze helpen u te voorkomen dat gevoelige informatie, zoals verbindingsreeksen in uw code of configuratiebestanden, worden opgenomen.

  • Ververleent minimale bevoegdheden aan elke nieuwe service. Wijs alleen de benodigde machtigingen toe aan elke nieuwe service-id. Als een identiteit bijvoorbeeld alleen naar een containerregister hoeft te pushen, geeft u deze geen pull-machtigingen. Controleer deze machtigingen regelmatig en pas ze indien nodig aan. Gebruik verschillende identiteiten voor verschillende rollen, zoals implementatie en de toepassing. Als u dit doet, beperkt u de mogelijke schade als één identiteit wordt aangetast.

  • Infrastructuur gebruiken als code (IaC). Gebruik Bicep of een vergelijkbaar IaC-hulpprogramma zoals Terraform om uw cloudresources te definiëren en te beheren. IaC zorgt voor een consistente toepassing van beveiligingsconfiguraties in uw implementaties en stelt u in staat om de installatie van uw infrastructuur te beheren.

Volg deze aanbevelingen om verificatie en autorisatie voor gebruikers (gebruikersidentiteiten) te configureren:

  • Geef gebruikers minimale bevoegdheden. Net als bij services moet u ervoor zorgen dat gebruikers alleen beschikken over de machtigingen die ze nodig hebben om hun taken uit te voeren. Controleer en pas deze machtigingen regelmatig aan.

  • Voer regelmatig beveiligingscontroles uit. Controleer uw beveiligingsconfiguratie regelmatig en controleer deze. Zoek naar onjuiste configuraties en onnodige machtigingen en corrigeer of verwijder ze onmiddellijk.

De referentie-implementatie maakt gebruik van IaC om beheerde identiteiten toe te wijzen aan toegevoegde services en specifieke rollen aan elke identiteit. Het definieert rollen en machtigingentoegang voor implementatie door rollen te definiëren voor Container Registry-pushes en pulls. Dit is de code:

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
}

Onafhankelijke automatische schaalaanpassing configureren

Het patroon Moderne web-app begint de monolithische architectuur op te splitsen en introduceert serviceontsleuteling. Wanneer u een web-app-architectuur loskoppelt, kunt u losgekoppelde services onafhankelijk schalen. Door de Azure-services te schalen ter ondersteuning van een onafhankelijke web-app-service, in plaats van een volledige web-app, worden de schaalkosten geoptimaliseerd terwijl aan de eisen wordt tegemoet getreden. Volg deze aanbevelingen om containers automatisch te schalen:

  • Gebruik stateless services. Zorg ervoor dat uw services staatloos zijn. Als uw web-app de sessiestatus in behandeling bevat, moet u deze externaliseren naar een gedistribueerde cache zoals Redis of een database zoals SQL Server.

  • Configureer regels voor automatisch schalen. Gebruik de configuraties voor automatisch schalen die de meest rendabele controle over uw services bieden. Voor containerservices biedt schaalaanpassing op basis van gebeurtenissen, zoals Kubernetes Event-Driven Autoscaler (KEDA), vaak gedetailleerde controle waarmee u kunt schalen op basis van metrische gegevens van gebeurtenissen. Container Apps en AKS ondersteunen KEDA. Voor services die KEDA niet ondersteunen, zoals App Service, gebruikt u de functies voor automatisch schalen die door het platform zelf worden geleverd. Deze functies omvatten vaak schalen op basis van regels op basis van metrische gegevens of HTTP-verkeer.

  • Configureer minimale replica's. Als u koude start wilt voorkomen, configureert u instellingen voor automatisch schalen om minimaal één replica te onderhouden. Een koude start is de initialisatie van een service vanaf een gestopte status. Een koude start vertraagt vaak het antwoord. Als het minimaliseren van de kosten een prioriteit is en u koude startvertragingen kunt tolereren, stelt u het minimumaantal replica's in op 0 wanneer u automatische schaalaanpassing configureert.

  • Configureer een afkoelperiode. Pas een geschikte afkoelperiode toe om een vertraging tussen schaalgebeurtenissen te introduceren. Het doel is om overmatige schaalactiviteiten te voorkomen die worden geactiveerd door tijdelijke belastingpieken.

  • Schaalaanpassing op basis van wachtrijen configureren. Als uw toepassing gebruikmaakt van een berichtenwachtrij zoals Service Bus, configureert u de instellingen voor automatisch schalen om te schalen op basis van de lengte van de aanvraagberichtwachtrij. De scaler probeert één replica van de service te onderhouden voor elke N berichten in de wachtrij (afgerond).

De referentie-implementatie maakt bijvoorbeeld gebruik van de Service Bus KEDA-schaalfunctie om container-app automatisch te schalen op basis van de lengte van de Service Bus-wachtrij. Met de schaalregel, met de naam service-bus-queue-length-rule, wordt het aantal servicereplica's aangepast op basis van het aantal berichten in de opgegeven Service Bus-wachtrij. De parameter messageCount is ingesteld op 10, waarmee de schaalschaal wordt geconfigureerd om één replica toe te voegen voor elke 10 berichten in de wachtrij. Het maximumaantal replica's (max_replicas) is ingesteld op 10. Het minimale aantal replica's is impliciet 0, tenzij deze wordt overschreven. Met deze configuratie kan de service omlaag schalen naar nul wanneer er geen berichten in de wachtrij staan. De verbindingsreeks voor de Service Bus-wachtrij wordt opgeslagen als een geheim in Azure, met de naam azure-servicebus-connection-string, die wordt gebruikt voor het verifiëren van de scaler bij de Service Bus. Dit is de Terraform-code:

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

Service-implementatie containeriseren

Containerisatie is de inkapseling van alle afhankelijkheden die de app nodig heeft in een lichtgewicht installatiekopie die betrouwbaar kan worden geïmplementeerd op een breed scala aan hosts. Volg deze aanbevelingen om de implementatie in containers te plaatsen:

  • Domeingrenzen identificeren. Begin met het identificeren van de domeingrenzen in uw monolithische toepassing. Hierdoor kunt u bepalen welke onderdelen van de toepassing u in afzonderlijke services kunt extraheren.

  • Docker-installatiekopieën maken. Wanneer u Docker-installatiekopieën voor uw Java-services maakt, gebruikt u officiële OpenJDK-basisinstallatiekopieën. Deze installatiekopieën bevatten alleen de minimale set pakketten die java moet uitvoeren. Als u deze afbeeldingen gebruikt, worden zowel de pakketgrootte als het kwetsbaarheid voor aanvallen geminimaliseerd.

  • Gebruik Dockerfiles met meerdere fasen. Gebruik een Dockerfile met meerdere fasen om buildtime-assets te scheiden van de runtimecontainerinstallatiekopieën. Met dit type bestand kunt u uw productie-installatiekopieën klein en veilig houden. U kunt ook een vooraf geconfigureerde buildserver gebruiken en het JAR-bestand kopiëren naar de containerinstallatiekopie.

  • Uitvoeren als een niet-basisgebruiker. Voer uw Java-containers uit als een niet-basisgebruiker (via gebruikersnaam of UID $APP_UID) om te voldoen aan het principe van minimale bevoegdheden. Hierdoor worden de mogelijke gevolgen van een geïnfecteerde container beperkt.

  • Luister op poort 8080. Wanneer u containers uitvoert als een niet-basisgebruiker, configureert u uw toepassing om te luisteren op poort 8080. Dit is een algemene conventie voor niet-basisgebruikers.

  • Afhankelijkheden inkapselen. Zorg ervoor dat alle afhankelijkheden die de app nodig heeft, worden ingekapseld in de Docker-containerinstallatiekopieën. Dankzij inkapseling kan de app betrouwbaar worden geïmplementeerd op een breed scala aan hosts.

  • Kies de juiste basisafbeeldingen. De basisinstallatiekopieën die u kiest, zijn afhankelijk van uw implementatieomgeving. Als u bijvoorbeeld implementeert in Container Apps, moet u Linux Docker-installatiekopieën gebruiken.

De referentie-implementatie demonstreert een Docker-buildproces voor het containeriseren van een Java-toepassing. Dockerfile maakt gebruik van een build met één fase met de OpenJDK-basisinstallatiekopieën (mcr.microsoft.com/openjdk/jdk:17-ubuntu), die de benodigde Java Runtime-omgeving biedt.

Het Dockerfile bevat de volgende stappen:

  1. Het volume declareren. Er wordt een tijdelijk volume (/tmp) gedefinieerd. Dit volume biedt tijdelijke bestandsopslag die gescheiden is van het hoofdbestandssysteem van de container.
  2. Artefacten kopiëren. Het JAR-bestand van de toepassing (email-processor.jar) wordt gekopieerd naar de container, samen met de Application Insights-agent (applicationinsights-agent.jar) die wordt gebruikt voor bewaking.
  3. Het invoerpunt instellen. De container is geconfigureerd om de toepassing uit te voeren met de Application Insights-agent ingeschakeld. De code gebruikt java -javaagent om de toepassing tijdens runtime te bewaken.

Het Dockerfile houdt de installatiekopieën klein door alleen runtime-afhankelijkheden op te bevatten. Het is geschikt voor implementatieomgevingen zoals Container Apps die ondersteuning bieden voor op Linux gebaseerde containers.

# 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"]

De referentie-implementatie implementeren

Implementeer de referentie-implementatie van het moderne web-app-patroon voor Java. Er zijn instructies voor zowel de ontwikkeling als de productie-implementatie in de opslagplaats. Nadat u de implementatie hebt geïmplementeerd, kunt u ontwerppatronen simuleren en observeren.

In het volgende diagram ziet u de architectuur van de referentie-implementatie:

diagram met de architectuur van de referentie-implementatie.

Download een Visio-bestand van deze architectuur.