Meine GraphQL-Fähigkeiten verbessern: Real-Time-Abonnements

Seit einigen Jahren versuche ich, Frameworks, Produkte und Dienstleistungen zu identifizieren, die Technologen dabei behilflich sind, ihre Konzentration auf die Verlängerung der Wertschöpfung ihrer geistigen Eigentumsrechte zu legen. Dies ist für mich immer noch eine herrliche Reise, voller einzigartiger Lernchancen.

Der Ingenieur in mir fragte kürzlich, ob es eine Situation gibt, in der ich ein sekundäres Nutzen für ein bestehendes Konzept finden könnte, das ich zuvor erwähnt habe. Mit anderen Worten, könnte ich ein anderes Nutzen identifizieren, das das gleiche Ausmaß an Auswirkungen wie die ursprüngliche Elternlösung hat?

In diesem Artikel möchte ich mich tiefer in GraphQL einarbeiten, um zu sehen, was ich finde kann.

In meinem Artikel „Wenn es Zeit ist, REST eine Pause zu geben“ beschrieb ich, dass es realistische Szenarien gibt, in denen GraphQL gegenüber einer RESTful-Dienstleistung bevorzugt werden kann. Wir haben gesehen, wie man ein GraphQL-API mit Apollo Server erstellen und deployieren kann.

In diesem Folged blog post planiere ich, meine Kenntnisse von GraphQL zu verbessern, indem ich durch Subskriptionen für den Echtzeitdatenabruf gehe. Wir werden auch ein WebSocket-Dienst entwickeln, um die Subskriptionen abzuschließen.

Zusammenfassung: Kunden-360-Anwendungsfall

Mein vorheriger Artikel konzentrierte sich auf einen Kunden-360-Anwendungsfall, wo die Kunden meiner fiktiven Firma die folgenden Datensammlungen pflegen:

  • Kundeninformationen
  • Adressinformationen
  • Kontaktdaten
  • Kreditattribute

Ein riesiger Vorteil beim Einsatz von GraphQL besteht darin, dass mit einer einzigen GraphQL-Anfrage alle notwendigen Daten für einen Kunden-Token (eindeutige Identität) abgerufen werden können.

JavaScript

 

type Query {
    addresses: [Address]
    address(customer_token: String): Address
    contacts: [Contact]
    contact(customer_token: String): Contact
    customers: [Customer]
    customer(token: String): Customer
    credits: [Credit]
    credit(customer_token: String): Credit
}

Beim Einsatz eines RESTful-Ansatzes hätte es mehrere Anfragen und Antworten bedurft, um die einzige (360°) Sicht des Kunden zusammengestellt. GraphQL bietet uns eine performantere Lösung.

Level-Up-Ziele

Um in jedem Aspekt des Lebens zu verbessern, muss man neue Ziele erreichen. Für meine eigenen Ziele hier bedeutet das:

  • Verstehen und Implementieren des subscriptions-Wertes innerhalb von GraphQL
  • Verwenden einer WebSocket-Implementierung, um einen GraphQL-Abonnement zu konsumieren

Der Einsatz von Abonnements anstelle von Abfragen und Vorgängen innerhalb von GraphQL ist die bevorzugte Methode, wenn folgende Bedingungen erfüllt sind:

  • Kleine, schrittweise Änderungen an großen Objekten
  • Niedriglatenzzeit, reale Zeit-Updates (wie in einer Chat-App)

Das ist wichtig, da die Implementierung von Abonnements innerhalb von GraphQL nicht einfach ist. Der zugrunde liegende Server muss aktualisiert werden, und die verbrauchte Anwendung erfordert ebenfalls eine Neugestaltung.

Glücklicherweise passt der Fall, den wir mit unserem Customer 360-Beispiel verfolgen, perfekt zu Abonnements. Außerdem werden wir eine WebSocket-Methode verwenden, um diese Abonnements zu nutzen.

Wie zuvor werde ich weiterhin Apollo verwenden.

Level-Up mit Abonnementen-Creds

Zunächst müssen wir die notwendigen Bibliotheken installieren, um Subskriptionen mit meinem Apollo GraphQL-Server zu unterstützen:

Shell

 

npm install ws
npm install graphql-ws @graphql-tools/schema
npm install graphql-subscriptions 

Mit den installierten Elementen konzentrierte ich mich darauf, den index.ts aus meiner ursprünglichen Datenbank aufzubauen, um die typedefs-Konstante mit folgendem zu erweitern:

JavaScript

 

type Subscription {
    creditUpdated: Credit
}

Ich stellte auch eine Konstante für eine neue PubSub-Instanz bereit und schuf einen Test-Abonnement, das wir später verwenden werden:

JavaScript

 

const pubsub = new PubSub();

pubsub.publish('CREDIT_BALANCE_UPDATED', {
    creditUpdated: {
    }
});

Ich bereinigte die bestehenden Resolver und fügte einen neuen Subscription für diese neuen Anwendungsfälle hinzu:

JavaScript

 

const resolvers = {
    Query: {
        addresses: () => addresses,
        address: (parent, args) => {
            const customer_token = args.customer_token;
            return addresses.find(address => address.customer_token === customer_token);
        },
        contacts: () => contacts,
        contact: (parent, args) => {
            const customer_token = args.customer_token;
            return contacts.find(contact => contact.customer_token === customer_token);
        },
        customers: () => customers,
        customer: (parent, args) => {
            const token = args.token;
            return customers.find(customer => customer.token === token);
        },
        credits: () => credits,
        credit: (parent, args) => {
            const customer_token = args.customer_token;
            return credits.find(credit => credit.customer_token === customer_token);
        }
    },
    Subscription: {
        creditUpdated: {
            subscribe: () => pubsub.asyncIterator(['CREDIT_BALANCE_UPDATED']),
        }
    }
};

Ich überarbeitete dann die Serverkonfiguration und führte das Abonnementdesign ein:

JavaScript

 

const app = express();
const httpServer = createServer(app);
const wsServer = new WebSocketServer({
    server: httpServer,
    path: '/graphql'
});

const schema = makeExecutableSchema({ typeDefs, resolvers });
const serverCleanup = useServer({ schema }, wsServer);

const server = new ApolloServer({
    schema,
    plugins: [
        ApolloServerPluginDrainHttpServer({ httpServer }),
        {
            async serverWillStart() {
                return {
                    async drainServer() {
                        serverCleanup.dispose();
                    }
                };
            }
        }
    ],
});

await server.start();

app.use('/graphql', cors(), express.json(), expressMiddleware(server, {
    context: async () => ({ pubsub })
}));

const PORT = Number.parseInt(process.env.PORT) || 4000;
httpServer.listen(PORT, () => {
    console.log(`Server is now running on http://localhost:${PORT}/graphql`);
    console.log(`Subscription is now running on ws://localhost:${PORT}/graphql`);
});

Um Kundeninitiierte Updates zu simulieren, schrieb ich die folgende Methode, um den Kreditbalanz jeder fünf Sekunden um $50 zu erhöhen, solange der Dienst ausgeführt wird. Sobald der Betrag die Kreditgrenze von $10.000 (oder darüber) erreicht oder überschritten hat, setzte ich den Betrag zurück auf $2.500, um eine Zahlung abzubrechen zu simulieren.

JavaScript

 

function incrementCreditBalance() {
    if (credits[0].balance >= credits[0].credit_limit) {
        credits[0].balance = 0.00;
        console.log(`Credit balance reset to ${credits[0].balance}`);

    } else {
        credits[0].balance += 50.00;
        console.log(`Credit balance updated to ${credits[0].balance}`);
    }

    pubsub.publish('CREDIT_BALANCE_UPDATED', { creditUpdated: credits[0] });
    setTimeout(incrementCreditBalance, 5000);
}

incrementCreditBalance();

Der vollständige index.ts-Datei kann hier gefunden werden.

Auf Heroku deployieren

Nun, da der Dienst bereit ist, ist es an der Zeit, den Dienst zu deployieren, damit wir uns mit ihm interagieren können. Da Heroku letzten Mal super funktioniert hat (und es mir leicht ist, es zu verwenden), lassen Sie uns diesen Weg verfolgen.

Um loslegen zu können, musste ich die folgenden Heroku CLI-Befehle ausführen:

Shell

 

$ heroku login
$ heroku create jvc-graphql-server-sub

Creating jvc-graphql-server-sub... done
https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/ | https://git.heroku.com/jvc-graphql-server-sub.git

Der Befehl fügte auch automatisch das von Heroku verwendete Repository als Remote hinzu:

Shell

 

$ git remote
heroku
origin

Wie ich in meinem vorherigen Artikel erwähnte, deaktiviert Apollo Server Apollo Explorer in Produktionsumgebungen. Um Apollo Explorer für unsere Bedürfnisse verfügbar zu halten, musste ich die Umgebungsvariable NODE_ENV auf Entwicklung setzen. Ich habe das mit folgender CLI-Befehlschaft erledigt:

Shell

 

$ heroku config:set NODE_ENV=development

Setting NODE_ENV and restarting jvc-graphql-server-sub... done, v3
NODE_ENV: development

Ich war bereit, meinen Code auf Heroku zu deployieren:

Shell

 

$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku

Ein schneller Blick auf das Heroku-Dashboard zeigte mir, dass mein Apollo Server ohne Probleme läuft:

In der Einstellungen -Sektion fand ich die URL des Heroku-App-Dienstes für diese Serviceinstanz:

https://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/

  • Bitte beachten: Dieser Link wird bereits zu der Zeit, wenn dieser Artikel veröffentlicht wurde, nicht mehr verfügbar sein.

Für den Augenblick konnte ich graphql an diese URL anhängen, um Apollo Server Studio zu starten. Dies ermöglichte es mir, die Subscriptions wie erwartet zu verwenden:

Beachten Sie die Subscription-Antworten auf der rechten Seite des Bildschirms.

Erweitern Sie Ihre Fähigkeiten mit WebSocket-Kenntnissen

Wir können die WebSocket-Unterstützung und die Fähigkeiten von Heroku nutzen, um eine Implementierung zu erstellen, die die von uns erstellte Subscription verwendet.

In meinem Fall habe ich ein index.js-Datei mit folgendem Inhalt erstellt. Grundsätzlich hat dies einen WebSocket-Client erstellt und auch einen leeren HTTP-Dienst, den ich verwenden konnte, um zu überprüfen, ob der Client läuft:

JavaScript

 

import { createClient } from "graphql-ws";
import { WebSocket } from "ws";
import http from "http";

// Erstellen eines leeren HTTP-Servers, um auf Heroku's $PORT zu binden
const PORT = process.env.PORT || 3000;
http.createServer((req, res) => res.end('Server is running')).listen(PORT, () => {
  console.log(`HTTP server running on port ${PORT}`);
});

const host_url = process.env.GRAPHQL_SUBSCRIPTION_HOST || 'ws://localhost:4000/graphql';

const client = createClient({
  url: host_url,
  webSocketImpl: WebSocket
});

const query = `subscription {
  creditUpdated {
    token
    customer_token
    credit_limit
    balance
    credit_score
  }
}`;

function handleCreditUpdated(data) {
  console.log('Received credit update:', data);
}

// Abonnieren der creditUpdated-Subscription
client.subscribe(
  {
    query,
  },
  {
    next: (data) => handleCreditUpdated(data.data.creditUpdated),
    error: (err) => console.error('Subscription error:', err),
    complete: () => console.log('Subscription complete'),
  }
);

Die komplette index.js Datei kann hier gefunden

werden. Wir können diese einfache Node.js-Anwendung auch auf Heroku deployieren, indem wir sicherstellen, die GRAPHQL_SUBSCRIPTION_HOST Umgebungsvariable auf die zuvor verwendete Heroku-App-URL einzustellen.

Ich habe auch den folgenden Procfile erstellt, um Heroku zu sagen, wie meine App gestartet werden soll:

Shell

 

web: node src/index.js

Danach habe ich eine neue Heroku-App erstellt:

Shell

 

$ heroku create jvc-websocket-example

Creating jvc-websocket-example... done
https://jvc-websocket-example-62824c0b1df4.herokuapp.com/ | https://git.heroku.com/jvc-websocket-example.git

Dann habe ich die GRAPHQL_SUBSCRIPTION_HOST Umgebungsvariable eingestellt, um auf meinen laufenden GraphQL-Server zu zeigen:

Shell

 

$ heroku --app jvc-websocket-example \
    config:set \
GRAPHQL_SUBSCRIPTION_HOST=ws://jvc-graphql-server-sub-1ec2e6406a82.herokuapp.com/graphql

An dieser Stelle sind wir bereit, unser Code auf Heroku zu deployen:

Shell

 

$ git commit --allow-empty -m 'Deploy to Heroku'
$ git push heroku

Sobald der WebSocket-Client startet, können wir seinen Status im Heroku-Dashboard sehen:

Durch die Ansicht der Logs im Heroku-Dashboard für die jvc-websocket-example-Instanz können wir die mehrfache Aktualisierung des balance-Eigenschafts des jvc-graphql-server-sub-Diensts sehen. In meiner Demo konnte ich sogar den Fall beobachten, in dem der Betrag auf null gesenkt wurde, simulierend, dass eine Zahlung getätigt wurde:

Im Terminal können wir mit dem CLI-Befehl heroku logs auf dieselben Logs zugreifen.

Shell

 

2024-08-28T12:14:48.463846+00:00 app[web.1]: Received credit update: {
2024-08-28T12:14:48.463874+00:00 app[web.1]:   token: 'credit-token-1',
2024-08-28T12:14:48.463875+00:00 app[web.1]:   customer_token: 'customer-token-1',
2024-08-28T12:14:48.463875+00:00 app[web.1]:   credit_limit: 10000,
2024-08-28T12:14:48.463875+00:00 app[web.1]:   balance: 9950,
2024-08-28T12:14:48.463876+00:00 app[web.1]:   credit_score: 750
2024-08-28T12:14:48.463876+00:00 app[web.1]: }

Nicht nur haben wir einen GraphQL-Dienst mit einer Abonnement-Implementierung running, sondern wir haben jetzt auch einen WebSocket-Client, der diese Aktualisierungen verbraucht.

Fazit

Meine Leser könnten meinen persönlichen Missionstatement erinnern, der meiner Meinung nach für jeden IT-Professional gelten kann:

“Fokusiere deine Zeit darauf, Features/Funktionalitäten bereitzustellen, die das Wert Ihrer geistigen Eigentum erweitern. Nutze Frameworks, Produkte und Dienstleistungen für alles andere.“

— J. Vester

In diesem Tiefgang in die GraphQL-Abonnements konnten wir erfolgreich Updates von einem auf Heroku laufenden Apollo Server verwenden, indem wir einen anderen auf Heroku laufenden Dienst verwendeten — eine auf Node.js basierende Anwendung, die WebSockets verwendet. Durch die Nutzung leichter Abonnements konnten wir vermeiden, Queries für unveränderte Daten zu senden, sondern abonnierten einfach, um Kreditguthaben-Updates zu erhalten, sobald sie passierten.

In der Einleitung habe ich erwähnt, dass ich nach einem zusätzlichen Wertprinzip in einem Thema suchte, das ich zuvor beschrieben habe. GraphQL-Abonnements sind ein hervorragendes Beispiel dafür, was ich im Sinn hatte, da sie Konsumenten erlauben, Updates sofort zu erhalten, ohne Queries gegen die Quelldaten auszuführen. Dies wird die Kunden der Customer 360-Daten sehr interessant machen, da sie Live-Updates erhalten können, sobald sie passieren.

Heroku ist ein weiteres Beispiel, das meiner Missionstatement folgt, indem es eine Plattform anbietet, die es mir ermöglicht, Lösungen mittels einer CLI und Standard-Git-Befehlen schnell zu prototypen. Dies gibt mir nicht nur eine einfache Möglichkeit, meinen Abonnementsexample zu zeigen, sondern auch, einen Konsumenten mit WebSockets zu implementieren.

Wenn du an den Quellcode für diesen Artikel interessiert bist, schaue in meinen Repositories auf GitLab nach:

Ich bin davon überzeugt, dass ich meine GraphQL-Fähigkeiten mit diesem Bemühen erfolgreich verbessert habe. Diese Reise war für mich neu und Herausfordernd — und auch sehr Spaßig!

Ich plane, mich nächstes Mal mit Authentifizierung zu befassen, was hoffentlich eine weitere Gelegenheit für ein Level-Up mit GraphQL und Apollo Server bietet. Bleibt angemeldet!

Habt ein wirklich großartiges Tag!

Source:
https://dzone.com/articles/leveling-up-my-graphql-skills-real-time-subscriptions