Lekérdezés alapú webalkalmazás korlátozásainak elemzése

Befejeződött

Lekérdezésalapú webalkalmazás.

Az alkalmazás jelenlegi architektúrája úgy jelenti a készletadatokat, hogy lekéri az összes részvényáradatot a kiszolgálóról egy időzítő alapján. Ezt a kialakítást általában lekérdezéses alapúnak hívjuk.

Kiszolgáló

A tőzsdei árfolyamadatok egy Azure Cosmos DB-adatbázisban vannak tárolva. HA EGY HTTP-kérés aktiválja, a függvény getStocks az adatbázis összes sorát visszaadja.

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,
        };
    },
});
  • Adatok lekérése: A kód első szakasza, a CosmosInput lekéri a stocks tábla összes elemét a lekérdezéssel SELECT * from cegyütt a stocksdb Cosmos DB adatbázisában.
  • Adatok visszaadása: A kód második szakasza, az app.http bemenetként fogadja ezeket az adatokat a függvénybe context.extraInputs , majd válasz törzsként adja vissza az ügyfélnek.

Ügyfél

A mintaügyfél Vue.js használ a felhasználói felület és a Beolvasás ügyfél megírásához az API-nak küldött kérések kezeléséhez.

A HTML-oldal időzítővel öt másodpercenként küld kérést a kiszolgálónak a készletek lekéréséhez. A válaszban egy tömböt kapunk a tőzsdei adatokkal, és ezt jelenítjük meg a felhasználónak.

<!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 A metódus lekérdezésének megkezdése után a update metódust öt másodpercenként hívjuk meg. A metóduson update belül a rendszer get kérést küld az /api/getStocks API-végpontnak, és az eredmény a app.stocksfelhasználói felületet frissíti.

A kiszolgáló és az ügyfélkód viszonylag egyszerű: az összes adat lekérése, az összes adat megjelenítése. Amint az elemzésünkből kiderül, ez az egyszerűség bizonyos korlátozásokat is magában hordoz.

Prototípus-megoldás elemzése

A Tailwind Traders mérnökeként azonosította ennek az időzítőalapú lekérdezési módszernek néhány hátrányait.

  • Szükségtelen API-kérések: Az időzítőalapú lekérdezési prototípusban az ügyfélalkalmazás kapcsolatba lép a kiszolgálóval, hogy vannak-e módosítások a mögöttes adatokon.

  • Szükségtelen oldalfrissítések: Miután a kiszolgálóról visszaadta az adatokat, a teljes készletlista frissül a weblapon, még akkor is, ha az adatok nem változtak. Ez a lekérdezéses megoldás tehát nem hatékony.

  • Lekérdezési időközök: A forgatókönyv legjobb lekérdezési időközének kiválasztása szintén kihívást jelent. A lekérdezésnél mindenképpen figyelembe kell vennünk, hogy milyen költségekkel járnak a háttérrendszerhez irányuló hívások, és el kell döntenünk, hogy milyen gyakran szeretnénk, hogy az alkalmazást új adatokra reagáljon. Az új adatok elérhetővé válása és az alkalmazás észlelése között gyakran előfordulnak késések. Az alábbi ábra ezt a problémát illusztrálja.

    Ábra egy ütemtervről és egy lekérdezési eseményindítóról, amely öt percenként ellenőrzi az új adatokat. Az új adatok hét perc elteltével válnak elérhetővé. Az alkalmazás nem ismeri az új adatokat a következő szavazásig, amely 10 percen belül megtörténik.

    Legrosszabb esetben az új adatok észlelésének késése megegyezik a lekérdezési időközzel. Érdemesebb tehát kisebb időközt használni?

  • Adatmennyiség: Az alkalmazás méretezése során az ügyfél és a kiszolgáló között kicserélt adatok mennyisége problémába ütközik. Minden egyes HTTP-kérelem fejléce több száz bájtnyi adatot tartalmaz, és ehhez hozzáadódik még a munkamenet cookie-ja is. Ezek mind jelentős terhelést és elpazarolt erőforrásokat jelentenek, különösen nagy forgalom mellett, és fölöslegesen veszik igénybe a kiszolgálót.

Most, hogy jobban ismeri a prototípust, ideje, hogy az alkalmazás futtassa a gépén.

A CORS támogatása

A Functions-alkalmazás local.settings.json fájljában a szakasz az Host alábbi beállításokat tartalmazza.

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

Ez a konfiguráció lehetővé teszi, hogy a localhost:3000 címen futó webalkalmazás kéréseket küldjön a localhost:7071 címen futó függvényalkalmazásnak. A tulajdonság CORSCredentials arra utasítja a függvényalkalmazást, hogy fogadja el a kérelemben szereplő hitelesítő cookie-kat.

Az eltérő eredetű erőforrások megosztása (CORS) egy olyan HTTP-szolgáltatás, amely egy adott tartományban futó webalkalmazás számára teszi lehetővé, hogy hozzáférjen egy másik tartomány erőforrásaihoz. A webböngészők azonoseredet-szabálynak nevezett biztonsági korlátozással akadályozzák meg, hogy egy adott weblap más tartományokból hívjon meg API-kat. A CORS biztonságos megoldást nyújt arra, hogy a forrástartományból más tartományokban lévő API-k legyenek meghívhatók.

Helyi futtatáskor a CORS a minta local.settings.json fájljában van konfigurálva, amely soha nem lesz közzétéve. Az ügyfélalkalmazás (7. egység) telepítésekor frissítenie kell a CORS-beállításokat is a függvényalkalmazásban, hogy engedélyezze az ügyfélalkalmazásból való hozzáférést.