Analýza omezení webové aplikace založené na dotazování

Dokončeno

webové aplikace založené na dotazování.

Aktuální architektura aplikace oznamuje informace o akciích načtením všech cen akcií ze serveru podle nastaveného časovače. Tento návrh se často označuje jako návrh založený na dotazování.

Server

Informace o cenách akcií se ukládají do databáze Azure Cosmos DB. Při aktivaci požadavkem HTTP funkce getStocks vrátí všechny řádky z databáze.

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,
        };
    },
});
  • Získat data: První část kódu, cosmosInput, získá všechny položky v tabulce stocks s dotazem SELECT * from cv databázi stocksdb ve službě Cosmos DB.
  • Návratová data: Druhá část kódu, app.http, přijímá tato data jako vstup do context.extraInputs a poté je vrátí jako tělo odpovědi zpět klientovi.

Klient

Ukázkový klient používá Vue.js k vytvoření uživatelského rozhraní a klienta Fetch pro zpracování požadavků na rozhraní API.

Stránka HTML používá časovač k odeslání požadavku na server každých pět sekund, aby požádala o akcie. Odpověď vrátí pole akcií, které se pak zobrazí uživateli.

<!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();
    }
});

Jakmile metoda startPoll začne dotazovat, volá se update metoda každých pět sekund. Uvnitř metody update se do koncového bodu rozhraní API /api/getStocks odešle požadavek GET a výsledek se nastaví na app.stocks, který aktualizuje uživatelské rozhraní.

Server a klientský kód jsou poměrně jednoduché: získat všechna data a zobrazit všechna data. Jak zjistíme v naší analýze, přináší tato jednoduchost určitá omezení.

Analýza prototypového řešení

Jako technik společnosti Tailwind Traders jste identifikovali některé nevýhody tohoto přístupu cyklického dotazování založeného na časovači.

  • nepotřebné požadavky rozhraní API: V prototypu dotazování založeném na časovači klientská aplikace kontaktuje server bez ohledu na to, jestli v podkladových datech existují změny.

  • Nepotřebné aktualizace stránky: Po vrácení dat ze serveru se na webové stránce aktualizuje celý seznam akcií, a to i v případě, že se nezměnila žádná data. Tento mechanismus dotazování je neefektivním řešením.

  • intervaly dotazování: Vybrat nejlepší interval dotazování pro váš scénář je také výzvou. Dotazování vás nutí rozhodnout se, kolik stojí každé volání do backendu a jak rychle chcete, aby vaše aplikace reagovala na nová data. Zpoždění často existují mezi časem, kdy jsou k dispozici nová data, a časem, kdy je aplikace detekuje. Následující obrázek ukazuje problém.

    Obrázek znázorňující časovou osu a trigger dotazu, který každých pět minut kontroluje nová data. Nová data budou k dispozici po sedmi minutách. Aplikace nezná nová data až do dalšího dotazování, ke kterému dochází v 10 minutách.

    V nejhorším případě se potenciální prodleva pro zjištění nových dat rovná intervalu dotazování. Proč tedy nepoužívat menší interval?

  • Množství dat: Při škálování aplikace se množství dat vyměňovaných mezi klientem a serverem stává problémem. Každá hlavička požadavku HTTP obsahuje stovky bajtů dat spolu se souborem cookie relace. Všechny tyto režijní náklady, zejména v případě velkého zatížení, vytvářejí nevyužité prostředky a zbytečně zatěžují server.

Teď, když jste obeznámeni s prototypem, je čas na spuštění aplikace na vašem počítači.

Podpora CORS

V local.settings.json souboru aplikace Functions obsahuje část Host následující nastavení.

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

Tato konfigurace umožňuje webové aplikaci spuštěné na localhost:3000 provádět požadavky na aplikaci funkcí běžící na localhost:7071. Vlastnost CORSCredentials aplikaci funkcí řekne, aby z požadavku přijímala soubory cookie s přihlašovacími údaji.

Sdílení prostředků mezi zdroji (CORS) je funkce HTTP, která umožňuje webové aplikaci spuštěné v jedné doméně přistupovat k prostředkům v jiné doméně. Webové prohlížeče implementují omezení zabezpečení známé jako zásady stejného původu, které brání webové stránce v volání rozhraní API v jiné doméně; CORS poskytuje bezpečný způsob, jak povolit, aby jedna doména (původní doména) volala rozhraní API v jiné doméně.

Při místním spuštění je CORS nakonfigurovaný pro vás v ukázkovém souboru local.settings.json, který se nikdy nepublikuje. Když nasadíte klientskou aplikaci (7. lekce), musíte také aktualizovat nastavení CORS v aplikaci funkcí tak, aby umožňovala přístup z klientské aplikace.