Während meiner jahrelangen Erfahrung mit der Entwicklung von Diensten war die RESTful API meine erste Wahl. Obwohl REST seine Vorteile hat, bedeutet das jedoch nicht, dass es für jeden Anwendungsfall der beste Ansatz ist. Im Laufe der Jahre habe ich gelernt, dass es für bestimmte Szenarien manchmal bessere Alternativen geben kann. Wenn ich nur deshalb an REST festhalte, weil ich davon begeistert bin – obwohl es nicht das Richtige ist –, führt das nur zu technischen Schulden und einem angespannten Verhältnis zum Produktbesitzer.
Einer der größten Schwachpunkte des RESTful-Ansatzes besteht darin, dass mehrere Anfragen gestellt werden müssen, um alle notwendigen Informationen für eine Geschäftsentscheidung abzurufen.
Nehmen wir als Beispiel an, ich möchte eine 360-Grad-Ansicht eines Kunden. Ich müsste die folgenden Anfragen stellen:
-
GET /customers/{some_token}
liefert die grundlegenden Kundeninformationen -
GET /addresses/{some_token}
liefert eine erforderliche Adresse -
GET /contacts/{some_token}
gibt die Kontaktinformationen zurück -
GET /credit/{some_token}
gibt wichtige Finanzinformationen zurück
Obwohl ich verstehe, dass das grundlegende Ziel von REST darin besteht, Antworten für jede Ressource laserfokussiert zu halten, bedeutet dieses Szenario mehr Arbeit auf der Verbraucherseite. Nur um eine Benutzeroberfläche zu füllen, die einer Organisation hilft, Entscheidungen im Zusammenhang mit zukünftigen Geschäften mit dem Kunden zu treffen, muss der Verbraucher mehrere Anrufe tätigen
In diesem Artikel zeige ich, warum GraphQL gegenüber einer RESTful-API der bevorzugte Ansatz ist, und demonstriere, wie Sie Apollo Server (und Apollo Explorer) einsetzen, um schnell mit GraphQL loszulegen.
Ich plane, meine Lösung mit Node.js zu erstellen und auf Heroku bereitzustellen.
Wann sollte GraphQL über REST verwendet werden?
Es gibt mehrere gängige Anwendungsfälle, in denen GraphQL ein besserer Ansatz als REST ist:
- Wenn Sie beim Abrufen von Daten Flexibilität benötigen: Sie können komplexe Daten aus verschiedenen Ressourcen abrufen, aber alle in einer einzigen Anfrage . (Ich werde in diesem Artikel näher auf diesen Weg eingehen.)
- Wenn das Frontend-Team die Benutzeroberfläche häufig weiterentwickeln muss: Sich schnell ändernde Datenanforderungen erfordern nicht, dass das Backend Endpunkte anpasst und Blockaden verursacht.
- Wenn Sie Over-Fetching und Under-Fetching minimieren möchten: Manchmal erfordert REST, dass Sie mehrere Endpunkte erreichen, um alle benötigten Daten zu erfassen (Under-Fetching), oder das Erreichen eines einzelnen Endpunkts gibt weit mehr Daten zurück, als Sie eigentlich benötigen (Over-Fetching).
- Wenn Sie mit komplexen Systemen und Mikrodiensten arbeiten: Manchmal müssen mehrere Quellen für ihre Daten nur auf eine einzige API-Ebene zugreifen. GraphQL kann diese Flexibilität durch einen einzigen API-Aufruf bereitstellen.
- Wenn Sie Echtzeitdaten benötigen: GraphQL bietet Abonnements , die Echtzeitaktualisierungen bereitstellen. Dies ist bei Chat-Apps oder Live-Datenfeeds nützlich. (Ich werde diesen Vorteil in einem Folgeartikel ausführlicher behandeln.)
Was ist Apollo Server?
Da meine Kenntnisse mit GraphQL nicht sehr ausgefeilt sind, habe ich mich für diesen Artikel für Apollo Server entschieden.
Apollo Server ist ein GraphQL-Server, der mit jedem GraphQL-Schema funktioniert. Ziel ist es, den Prozess der Erstellung einer GraphQL-API zu vereinfachen. Das zugrunde liegende Design lässt sich gut in Frameworks wie Express oder Koa integrieren. In meinem nächsten Artikel werde ich die Möglichkeit untersuchen, Abonnements (über die GraphQL-WS- Bibliothek) für Echtzeitdaten zu nutzen.
Wo Apollo Server wirklich glänzt, ist der Apollo Explorer, eine integrierte Weboberfläche, mit der Entwickler ihre GraphQL-APIs erkunden und testen können. Das Studio ist perfekt für mich, da es die einfache Erstellung von Abfragen und die Möglichkeit bietet, das API-Schema in einem grafischen Format anzuzeigen.
Mein Customer 360-Anwendungsfall
Für dieses Beispiel gehen wir davon aus, dass wir das folgende Schema benötigen, um eine 360-Grad-Ansicht des Kunden bereitzustellen:
type Customer { token: String name: String sic_code: String } type Address { token: String customer_token: String address_line1: String address_line2: String city: String state: String postal_code: String } type Contact { token: String customer_token: String first_name: String last_name: String email: String phone: String } type Credit { token: String customer_token: String credit_limit: Float balance: Float credit_score: Int }
Ich möchte mich auf die folgenden GraphQL-Abfragen konzentrieren:
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 }
Verbraucher geben das Token für den Kunden an, den sie anzeigen möchten. Wir erwarten, dass wir auch die entsprechenden Adress-, Kontakt- und Kreditobjekte abrufen. Das Ziel besteht darin, vier verschiedene API-Aufrufe für all diese Informationen zu vermeiden, anstatt einen einzigen API-Aufruf zu verwenden.
Erste Schritte mit Apollo Server
Ich begann damit, auf meiner lokalen Workstation einen neuen Ordner namens graphql-server-customer
zu erstellen. Anschließend befolgte ich mithilfe des Abschnitts „Erste Schritte“ der Apollo Server-Dokumentation die Schritte eins und zwei mit einem Typescript-Ansatz.
Als nächstes habe ich mein Schema definiert und auch einige statische Daten zum Testen eingefügt. Normalerweise würden wir eine Verbindung zu einer Datenbank herstellen, aber für diese Demo funktionieren statische Daten gut.
Unten ist meine aktualisierte index.ts
Datei:
import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; const typeDefs = `#graphql type Customer { token: String name: String sic_code: String } type Address { token: String customer_token: String address_line1: String address_line2: String city: String state: String postal_code: String } type Contact { token: String customer_token: String first_name: String last_name: String email: String phone: String } type Credit { token: String customer_token: String credit_limit: Float balance: Float credit_score: Int } 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 } `; const resolvers = { Query: { addresses: () => addresses, address: (parent, args, context) => { const customer_token = args.customer_token; return addresses.find(address => address.customer_token === customer_token); }, contacts: () => contacts, contact: (parent, args, context) => { const customer_token = args.customer_token; return contacts.find(contact => contact.customer_token === customer_token); }, customers: () => customers, customer: (parent, args, context) => { const token = args.token; return customers.find(customer => customer.token === token); }, credits: () => credits, credit: (parent, args, context) => { const customer_token = args.customer_token; return credits.find(credit => credit.customer_token === customer_token); } }, }; const server = new ApolloServer({ typeDefs, resolvers, }); const { url } = await startStandaloneServer(server, { listen: { port: 4000 }, }); console.log(`Apollo Server ready at: ${url}`); const customers = [ { token: 'customer-token-1', name: 'Acme Inc.', sic_code: '1234' }, { token: 'customer-token-2', name: 'Widget Co.', sic_code: '5678' } ]; const addresses = [ { token: 'address-token-1', customer_token: 'customer-token-1', address_line1: '123 Main St.', address_line2: '', city: 'Anytown', state: 'CA', postal_code: '12345' }, { token: 'address-token-22', customer_token: 'customer-token-2', address_line1: '456 Elm St.', address_line2: '', city: 'Othertown', state: 'NY', postal_code: '67890' } ]; const contacts = [ { token: 'contact-token-1', customer_token: 'customer-token-1', first_name: 'John', last_name: 'Doe', email: '[email protected]', phone: '123-456-7890' } ]; const credits = [ { token: 'credit-token-1', customer_token: 'customer-token-1', credit_limit: 10000.00, balance: 2500.00, credit_score: 750 } ];
Wenn alles wie erwartet konfiguriert ist, führen wir den folgenden Befehl aus, um den Server zu starten:
$ npm start
Da der Apollo-Server auf Port 4000 läuft, habe ich die URL http://localhost:4000/ verwendet, um auf Apollo Explorer zuzugreifen. Dann habe ich die folgende Beispielabfrage eingerichtet:
query ExampleQuery { addresses { token } contacts { token } customers { token } }
So sieht es im Apollo Explorer aus:
Durch Klicken auf die Schaltfläche „Beispielabfrage“ habe ich überprüft, ob die Antwortnutzlast mit den statischen Daten übereinstimmt, die ich in der index.ts
angegeben habe:
{ "data": { "addresses": [ { "token": "address-token-1" }, { "token": "address-token-22" } ], "contacts": [ { "token": "contact-token-1" } ], "customers": [ { "token": "customer-token-1" }, { "token": "customer-token-2" } ] } }
Bevor ich meinen Customer 360-Anwendungsfall weiter behandle, wollte ich diesen Dienst in der Cloud ausführen.
Bereitstellen von Apollo Server für Heroku
Da es in diesem Artikel darum geht, etwas Neues zu machen, wollte ich sehen, wie schwierig es wäre, meinen Apollo-Server auf Heroku bereitzustellen.
Ich wusste, dass ich die Unterschiede bei den Portnummern zwischen der lokalen Ausführung und der Ausführung irgendwo in der Cloud berücksichtigen musste. Ich habe meinen Code zum Starten des Servers wie folgt aktualisiert:
const { url } = await startStandaloneServer(server, { listen: { port: Number.parseInt(process.env.PORT) || 4000 }, });
Mit diesem Update verwenden wir Port 4000, sofern in einer Umgebungsvariablen kein PORT-Wert angegeben ist.
Mit Gitlab habe ich ein neues Projekt für diese Dateien erstellt und mich über die Heroku-Befehlszeilenschnittstelle (CLI) bei meinem Heroku-Konto angemeldet:
$ heroku login
Sie können eine neue App in Heroku entweder mit der CLI oder der Web-Benutzeroberfläche des Heroku-Dashboards erstellen. Für diesen Artikel verwenden wir die CLI:
$ heroku create jvc-graphql-server-customer
Der CLI-Befehl hat die folgende Antwort zurückgegeben:
Creating ⬢ jvc-graphql-server-customer... done https://jvc-graphql-server-customer-b62b17a2c949.herokuapp.com/ | https://git.heroku.com/jvc-graphql-server-customer.git
Der Befehl hat außerdem das von Heroku als Remote verwendete Repository automatisch hinzugefügt:
$ git remote heroku origin
Standardmäßig deaktiviert Apollo Server Apollo Explorer in Produktionsumgebungen. Für meine Demo möchte ich es auf Heroku laufen lassen. Dazu muss ich die Umgebungsvariable NODE_ENV
auf Entwicklung setzen. Ich kann das mit dem folgenden CLI-Befehl einstellen:
$ heroku config:set NODE_ENV=development
Der CLI-Befehl hat die folgende Antwort zurückgegeben:
Setting NODE_ENV and restarting ⬢ jvc-graphql-server-customer... done, v3 NODE_ENV: development
Jetzt können wir unseren Code auf Heroku bereitstellen:
$ git commit --allow-empty -m 'Deploy to Heroku' $ git push heroku
Ein kurzer Blick auf das Heroku-Dashboard zeigt, dass mein Apollo-Server ohne Probleme läuft:
Wenn Sie mit Heroku noch nicht vertraut sind, zeigt Ihnen diese Anleitung , wie Sie ein neues Konto erstellen und die Heroku CLI installieren.
Akzeptanzkriterien erfüllt: Mein Customer 360-Beispiel
Mit GraphQL kann ich die Akzeptanzkriterien für meinen Customer 360-Anwendungsfall mit der folgenden Abfrage erfüllen:
query CustomerData($token: String) { customer(token: $token) { name sic_code token }, address(customer_token: $token) { token customer_token address_line1 address_line2 city state postal_code }, contact(customer_token: $token) { token, customer_token, first_name, last_name, email, phone }, credit(customer_token: $token) { token, customer_token, credit_limit, balance, credit_score } }
Ich muss lediglich eine einzelne Customer- token
Variable mit dem Wert customer-token-1
übergeben:
{ "token": "customer-token-1" }
Wir können alle Daten mit einem einzigen GraphQL-API-Aufruf abrufen:
{ "data": { "customer": { "name": "Acme Inc.", "sic_code": "1234", "token": "customer-token-1" }, "address": { "token": "address-token-1", "customer_token": "customer-token-1", "address_line1": "123 Main St.", "address_line2": "", "city": "Anytown", "state": "CA", "postal_code": "12345" }, "contact": { "token": "contact-token-1", "customer_token": "customer-token-1", "first_name": "John", "last_name": "Doe", "email": "[email protected]", "phone": "123-456-7890" }, "credit": { "token": "credit-token-1", "customer_token": "customer-token-1", "credit_limit": 10000, "balance": 2500, "credit_score": 750 } } }
Unten sehen Sie einen Screenshot von Apollo Explorer, der von meiner Heroku-App ausgeführt wird:
Abschluss
Ich erinnere mich an die Zeit zu Beginn meiner Karriere, als Java und C# um die Akzeptanz bei Entwicklern konkurrierten. Befürworter beider Seiten waren bereit zu beweisen, dass die von ihnen gewählte Technologie die beste Wahl war … selbst wenn dies nicht der Fall war.
In diesem Beispiel hätten wir meinen Customer 360-Anwendungsfall auf mehrere Arten erfüllen können. Die Verwendung einer bewährten RESTful-API hätte funktioniert, aber es wären mehrere API-Aufrufe erforderlich gewesen, um alle erforderlichen Daten abzurufen. Durch die Verwendung von Apollo Server und GraphQL konnte ich meine Ziele mit einem einzigen API-Aufruf erreichen.
Mir gefällt auch, wie einfach es ist, meinen GraphQL-Server mit nur wenigen Befehlen in meinem Terminal auf Heroku bereitzustellen. So kann ich mich auf die Implementierung konzentrieren – ich lade die Last der Infrastruktur ab und kann meinen Code bei einem vertrauenswürdigen Drittanbieter ausführen. Und das Wichtigste: Dies entspricht genau meinem persönlichen Leitbild:
„Konzentrieren Sie sich auf die Bereitstellung von Features/Funktionalitäten, die den Wert Ihres geistigen Eigentums steigern. Nutzen Sie Frameworks, Produkte und Services für alles andere.“
– J. Vester
Wenn Sie am Quellcode dieses Artikels interessiert sind, ist er auf GitLab verfügbar.
Aber warten Sie … es gibt noch mehr!
In meinem Folgebeitrag werden wir unseren GraphQL-Server weiter ausbauen, um Authentifizierung und Echtzeit-Datenabruf mit Abonnements zu implementieren.
Habt einen richtig schönen Tag!