Analysera begränsningarna för en avsökningsbaserad webbapp

Slutförd

Avsökningsbaserat webbprogram.

Programmets aktuella arkitektur rapporterar aktieinformation genom att hämta all aktiekursinformation från servern baserat på en timer. Den här designen kallas ofta för avsökningsbaserad design.

Server

Aktiekursinformationen lagras i en Azure Cosmos DB-databas. När den utlöses av en HTTP-begäran returnerar funktionen getStocks alla rader från databasen.

import { app, input } from "@azure/functions";

const cosmosInput = input.cosmosDB({
    databaseName: 'stocksdb',
    containerName: 'stocks',
    connection: 'COSMOSDB_CONNECTION_STRING',
    sqlQuery: 'SELECT * from c',
});

app.http('getStocks', {
    methods: ['GET'],
    authLevel: 'anonymous',
    extraInputs: [cosmosInput],
    handler: (request, context) => {
        const stocks = context.extraInputs.get(cosmosInput);
        
        return {
            jsonBody: stocks,
        };
    },
});
  • Hämta data: Det första avsnittet av kod, cosmosInput, hämtar alla objekt i stocks tabellen, med frågan SELECT * from c, i stocksdb databasen i Cosmos DB.
  • Returnera data: Det andra kodavsnittet, app.http, tar emot dessa data i funktionen som indata i context.extraInputs och returnerar dem sedan som svarstext tillbaka till klienten.

Klient

Exempelklienten använder Vue.js för att skapa användargränssnittet och Fetch-klienten för att hantera begäranden till API:et.

HTML-sidan använder en timer för att skicka en begäran till servern var femte sekund för att begära lager. Svaret returnerar en matris med aktier, som sedan visas för användaren.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css" integrity="sha256-8B1OaG0zT7uYA572S2xOxWACq9NXYPQ+U5kHPV1bJN4=" crossorigin="anonymous" />
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
    <link rel="stylesheet" href="style.css">
    <title>Stocks | Enable automatic updates in a web application using Azure Functions and SignalR</title>
</head>
<body>
    
    <!-- BEGIN: Replace markup in this section -->
    <div id="app" class="container">
        <h1 class="title">Stocks</h1>
        <div id="stocks">
            <div v-for="stock in stocks" class="stock">
                <div class="lead">{{ stock.symbol }}: ${{ stock.price }}</div>
                <div class="change">Change:
                    <span :class="{ 'is-up': stock.changeDirection === '+', 'is-down': stock.changeDirection === '-' }">
                        {{ stock.changeDirection }}{{ stock.change }}
                    </span></div>
            </div>
        </div>
    </div>
    <!-- END  -->

    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js" integrity="sha256-chlNFSVx3TdcQ2Xlw7SvnbLAavAQLO0Y/LBiWX04viY=" crossorigin="anonymous"></script>
    <script src="bundle.js" type="text/javascript"></script>
</body>
</html>
import './style.css';

function getApiUrl() {

    const backend = process.env.BACKEND_URL;
    
    const url = (backend) ? `${backend}` : ``;
    return url;
}

const app = new Vue({
    el: '#app',
    interval: null,
    data() { 
        return {
            stocks: []
        }
    },
    methods: {
        async update() {
            try {
                
                const url = `${getApiUrl()}/api/getStocks`;
                console.log('Fetching stocks from ', url);

                const response = await fetch(url);
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }

                app.stocks = await response.json();
            } catch (ex) {
                console.error(ex);
            }
        },
        startPoll() {
            this.interval = setInterval(this.update, 5000);
        }
    },
    created() {
        this.update();
        this.startPoll();
    }
});

startPoll När metoden börjar avsöka update anropas metoden var femte sekund. update I metoden skickas en GET-begäran till API-slutpunkten /api/getStocks och resultatet är inställt på app.stocks, vilket uppdaterar användargränssnittet.

Server- och klientkoden är relativt enkel: hämta alla data, visa alla data. Som vi får reda på i vår analys medför den här enkelheten vissa begränsningar.

Analys av prototyplösning

Som Tailwind Traders-tekniker har du identifierat några av nackdelarna med den här tidsbaserade avsökningsmetoden.

  • Onödiga API-begäranden: I den tidsbaserade avsökningsprototypen kontaktar klientprogrammet servern oavsett om det finns ändringar i underliggande data eller inte.

  • Onödiga siduppdateringar: När data returneras från servern uppdateras hela listan med aktier på webbsidan, även om inga data har ändrats. Den här avsökningsmekanismen är en ineffektiv lösning.

  • Avsökningsintervall: Att välja det bästa avsökningsintervallet för ditt scenario är också en utmaning. Avsökning tvingar dig att välja mellan hur mycket varje anrop till serverdelen kostar och hur snabbt du vill att appen svarar på nya data. Fördröjningar finns ofta mellan den tid då nya data blir tillgängliga och den tid då appen identifierar dem. Problemet visas i följande bild.

    En bild som visar en tidslinje och en avsökningsutlösare som söker efter nya data var femte minut. Nya data blir tillgängliga efter sju minuter. Appen är inte medveten om de nya data förrän nästa avsökning, som inträffar på 10 minuter.

    I värsta fall är den potentiella fördröjningen vid identifiering av nya data lika med sökningsintervallet. Varför då inte använda ett mindre intervall?

  • Mängd data: När programmet skalas blir mängden data som utbyts mellan klienten och servern ett problem. Varje HTTP-frågehuvud innehåller hundratals byte med data tillsammans med sessionens cookie. All denna belastning, särskilt tung, leder till slösade resurser och anstränger servern i onödan.

Nu när du är mer bekant med prototypen är det dags att köra programmet på datorn.

Stöd för CORS

I den local.settings.json filen i Functions-appen Host innehåller avsnittet följande inställningar.

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "<STORAGE_CONNECTION_STRING>",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
    "COSMOSDB_CONNECTION_STRING": "<COSMOSDB_CONNECTION_STRING>"
  },
  "Host" : {
    "LocalHttpPort": 7071,
    "CORS": "http://localhost:3000",
    "CORSCredentials": true
  }
}

Med den här konfigurationen kan ett webbprogram som körs på localhost:3000 göra begäranden till funktionsappen som körs på localhost:7071. Egenskapen CORSCredentials instruerar funktionsappen att acceptera cookies för autentiseringsuppgifter från begäran.

CORS (Cross-origin resource sharing) är en HTTP-funktion som gör det möjligt för ett webbprogram som körs i en domän att komma åt resurser i en annan domän. Webbläsare implementerar en säkerhetsbegränsning som har samma ursprungsprincip som förhindrar att en webbsida anropar API:er i en annan domän. CORS är ett säkert sätt att tillåta en domän (ursprungsdomänen) att anropa API:er i en annan domän.

När du kör lokalt konfigureras CORS åt dig i exemplets local.settings.json-fil, som aldrig publiceras. När du distribuerar klientappen (enhet 7) måste du även uppdatera CORS-inställningarna i funktionsappen för att tillåta åtkomst från klientappen.