Aufbau eines Frontend-optimierten BFF für Microservices mit JavaScript
Olivia Novak
Dev Intern · Leapcell

Einleitung
In der sich entwickelnden Landschaft der modernen Webentwicklung, insbesondere mit der weit verbreiteten Einführung von Microservice-Architekturen, stehen Frontend-Anwendungen oft vor einer Vielzahl von Herausforderungen. Der direkte Konsum mehrerer Backend-Dienste kann zu komplexer Datenaggregation auf Client-Seite, übermäßigen API-Aufrufen und inkonsistenten Datenformaten führen. Diese Komplexität verlangsamt nicht nur die Frontend-Entwicklung, sondern beeinträchtigt auch negativ die Anwendungsleistung. Die Notwendigkeit einer dedizierten Vermittlungsschicht, die diese Backend-Komplexitäten abstrahieren und die Datenbereitstellung für spezifische Frontend-Bedürfnisse optimieren kann, ist von größter Bedeutung geworden. Genau hier glänzt das Backend-for-Frontend (BFF)-Muster und bietet eine maßgeschneiderte Lösung für diese Probleme. Dieser Artikel befasst sich damit, wie Sie mit JavaScript eine effektive, frontend-optimierte BFF-Schicht aufbauen und damit Ihren Entwicklungsprozess rationalisieren und die Benutzererfahrung verbessern können.
Grundlegende Konzepte verstehen
Bevor wir uns mit den Implementierungsdetails befassen, lassen Sie uns einige grundlegende Begriffe klären, die für das Verständnis des BFF-Musters entscheidend sind:
- Microservices-Architektur: Ein Designansatz, bei dem eine Anwendung als eine Suite kleiner, unabhängiger Dienste aufgebaut ist, von denen jeder in seinem eigenen Prozess läuft und über leichte Mechanismen, oft HTTP-APIs, kommuniziert. Dies bietet Skalierbarkeit, Ausfallsicherheit und unabhängige Bereitstellbarkeit.
- Backend for Frontend (BFF): Ein Designmuster, bei dem ein separater Backend-Dienst speziell für den Konsum durch einen bestimmten Frontend-Client (z. B. eine Web-App, eine mobile App, ein Admin-Panel) erstellt wird. Im Gegensatz zu einer herkömmlichen monolithischen API ist ein BFF für die spezifischen Daten- und Interaktionsmuster optimiert, die dieses Frontend benötigt.
- API-Gateway: Ein einziger Einstiegspunkt für alle Clients, der Anfragen bearbeitet, indem er sie an die entsprechenden Microservices weiterleitet, Authentifizierung, Ratenbegrenzung und andere übergreifende Belange verwaltet. Während ein BFF gelegentlich Funktionalitäten ähnlich einem API-Gateway integrieren kann, liegt sein Hauptaugenmerk auf der Frontend-spezifischen Optimierung, während ein API-Gateway eher für allgemeine Zwecke gedacht ist.
- Datenaggregation: Der Prozess des Sammelns von Daten aus mehreren Quellen (in diesem Fall verschiedene Microservices) und deren Kombination zu einer einzigen, umfassenden Antwort.
- Datentransformation: Der Prozess der Konvertierung von Daten von einem Format oder einer Struktur in eine andere, oft um den Bedürfnissen der konsumierenden Anwendung besser gerecht zu werden.
Die Kernidee eines BFF ist es, eine stark angepasste API für ein bestimmtes Frontend bereitzustellen, wodurch der „Impedanzfehlanpassung“ zwischen Backend-Diensten und Frontend-Anforderungen verringert wird.
Prinzipien eines Frontend-optimierten BFF
Ein gut gestaltetes BFF folgt mehreren Schlüsselprinzipien:
- Frontend-spezifische API: Das BFF stellt eine API zur Verfügung, die genau auf die Bedürfnisse eines bestimmten Frontends zugeschnitten ist. Das bedeutet, Daten zu aggregieren, Payloads zu transformieren und sogar spezifische Geschäftslogik zu implementieren, die das Frontend benötigt, anstatt rohe Microservice-APIs offenzulegen.
- Reduzierter Netzwerklast: Durch die Aggregation von Daten aus mehreren Microservices in einem einzigen Anfrage/Antwort-Zyklus reduziert das BFF die Anzahl der HTTP-Anfragen, die ein Frontend stellen muss, erheblich, was die Ladezeiten und die Leistung verbessert.
- Vereinfachte Frontend-Entwicklung: Frontend-Entwickler können mit einer einfacheren, kohärenteren API interagieren und sind somit von den Komplexitäten der Orchestrierung mehrerer Backend-Aufrufe und der Verarbeitung unterschiedlicher Datenformate befreit.
- Entkopplung: Das BFF hilft, das Frontend von der zugrunde liegenden Microservice-Architektur zu entkoppeln. Änderungen an den Microservices erfordern nicht zwangsläufig Änderungen am Frontend, sondern nur am BFF.
- Verbesserte Sicherheit: Das BFF kann als Gateway fungieren und eine zusätzliche Sicherheitsebene bieten, indem es Anfragen authentifiziert, den Zugriff auf bestimmte Daten autorisiert und Eingaben bereinigt, bevor es sie an Microservices weiterleitet.
Implementierung eines BFF mit JavaScript
JavaScript ist mit seiner allgegenwärtigen Präsenz sowohl auf Client- als auch auf Serverseite (Node.js) eine ausgezeichnete Wahl für den Aufbau von BFFs. Die asynchrone, ereignisgesteuerte Natur von Node.js eignet sich besonders gut für E/A-intensive Aufgaben wie das Tätigen mehrerer API-Aufrufe an vorgelagerte Dienste.
Betrachten wir ein praktisches Beispiel: eine E-Commerce-Anwendung, die auf einer einzelnen Produktdetailseite Produktinformationen, Kundenbewertungen und empfohlene Artikel anzeigen muss. In einer Microservice-Architektur können diese Informationen jeweils von einem Produkt-Service
, einem Bewertungs-Service
und einem Empfehlungs-Service
stammen.
Projekteinrichtung
Wir verwenden Express.js
als unser Web-Framework für das BFF und axios
für HTTP-Anfragen an vorgelagerte Microservices.
Initialisieren Sie zuerst ein neues Node.js-Projekt:
mkdir my-bff-service cd my-bff-service npm init -y npm install express axios dotenv
Erstellen Sie eine .env
-Datei zum Speichern von Microservice-URLs:
PRODUCT_SERVICE_URL=http://localhost:3001/products
REVIEW_SERVICE_URL=http://localhost:3002/reviews
RECOMMENDATION_SERVICE_URL=http://localhost:3003/recommendations
Grundlegende BFF-Struktur
Unser server.js
dient als Einstiegspunkt für das BFF.
require('dotenv').config(); // Umgebungsvariablen laden const express = require('express'); const axios = require('axios'); const app = express(); const PORT = process.env.PORT || 4000; app.use(express.json()); // JSON-Body-Parsing aktivieren // Middleware für grundlegende Fehlerbehandlung app.use((err, req, res, next) => { console.error(err.stack); res.status(500).send('Etwas ist schiefgegangen!'); }); // Routen für spezifische Frontend-Bedürfnisse definieren app.get('/api/product-details/:productId', async (req, res, next) => { try { const productId = req.params.productId; // Produktinformationen abrufen const productResponse = await axios.get(`${process.env.PRODUCT_SERVICE_URL}/${productId}`); const productData = productResponse.data; // Bewertungen für das Produkt abrufen const reviewsResponse = await axios.get(`${process.env.REVIEW_SERVICE_URL}?productId=${productId}`); const reviewsData = reviewsResponse.data; // Empfehlungen für das Produkt abrufen (Beispiel: basierend auf der Produktkategorie) // Dies könnte in realen Szenarien ein komplexerer Aufruf sein const recommendationsResponse = await axios.get(`${process.env.RECOMMENDATION_SERVICE_URL}?category=${productData.category || 'general'}`); const recommendationsData = recommendationsResponse.data; // Daten für das Frontend aggregieren und transformieren const consolidatedData = { id: productData.id, name: productData.name, description: productData.description, price: productData.price, imageUrl: productData.imageUrl, category: productData.category, reviews: reviewsData.map(review => ({ reviewer: review.user, rating: review.rating, comment: review.comment, date: new Date(review.timestamp).toLocaleDateString() })), // Beispieltransformation recommendedProducts: recommendationsData.map(reco => ({ id: reco.id, name: reco.title, thumbnail: reco.image })) // Beispieltransformation }; res.json(consolidatedData); } catch (error) { console.error('Fehler beim Abrufen der Produktdetails:', error.message); // Fehler an unsere Fehlerbehandlungs-Middleware übergeben next(error); } }); // BFF-Server starten app.listen(PORT, () => { console.log(`BFF Service läuft auf Port ${PORT}`); console.log(`Produktdetails abrufen unter http://localhost:${PORT}/api/product-details/:productId`); });
Erklärung und Verfeinerungen
-
Umgebungsvariablen: Wir verwenden
dotenv
, um Service-URLs aus einer.env
-Datei zu laden, wodurch sensible Informationen aus dem Codebase ferngehalten und die Konfigurationsverwaltung erleichtert werden. -
Express-Route: Der Endpunkt
/api/product-details/:productId
ist speziell für die Produktseite im Frontend konzipiert. Er erwartet einenproductId
-Parameter. -
Parallele API-Aufrufe: Für eine bessere Leistung sollten parallele API-Aufrufe in Betracht gezogen werden, wenn die von einem Dienst abgerufenen Daten nicht vom Ergebnis eines anderen abhängen. Zum Beispiel kann
Promise.all
verwendet werden:// ... innerhalb des Routen-Handlers const [productResponse, reviewsResponse, recommendationsResponse] = await Promise.all([ axios.get(`${process.env.PRODUCT_SERVICE_URL}/${productId}`), axios.get(`${process.env.REVIEW_SERVICE_URL}?productId=${productId}`), axios.get(`${process.env.RECOMMENDATION_SERVICE_URL}?category=${productCategory}`) // Kategorie benötigt möglicherweise vorherigen Abruf oder Standardwert ]); // ... Rest des Codes
Hinweis: In unserem Beispiel hängt
recommendationsResponse
vonproductData.category
ab, sodass ein direkterPromise.all
-Aufruf für alle drei möglicherweise nicht durchführbar ist, ohne einen initialen Produktruf oder eine Standardkategorie. Eine robustere parallele Strategie wäre, zuerst die Produktdaten abzurufen, dann Beweertungen und Empfehlungen parallel unter Verwendung der Produktkategorie abzurufen. -
Datenaggregation und -transformation:
- Das BFF tätigt einzelne Aufrufe an den
Produkt-Service
,Bewertungs-Service
undEmpfehlungs-Service
. - Anschließend aggregiert es die Antworten zu einem einzigen
consolidatedData
-Objekt. - Entscheidend ist, dass es die Daten transformiert. Beispielsweise können
reviewsData
eintimestamp
-Feld vom Backend enthalten, das Frontend könnte jedoch ein formatiertesdate
bevorzugen. Ebenso könnenrecommendations
title
undimage
verwenden, während das Frontendname
undthumbnail
erwartet. Dies stellt sicher, dass das Frontend die Daten in der exakten Form erhält, die es benötigt, und reduziert die Datenmanipulation auf der Frontend-Seite.
- Das BFF tätigt einzelne Aufrufe an den
-
Fehlerbehandlung: Eine grundlegende Fehlerbehandlung ist enthalten und zeigt, wie das BFF Fehler von vorgelagerten Diensten ordnungsgemäß verwalten und dem Frontend aussagekräftiges Feedback geben kann.
-
Authentifizierung/Autorisierung: Obwohl nicht explizit gezeigt, ist ein BFF ein idealer Ort, um Frontend-spezifische Authentifizierungs- und Autorisierungslogik zu implementieren. Zum Beispiel könnte es Benutzertoken validieren, bevor es Anfragen weiterleitet, oder benutzerspezifische Header für Microservices einfügen.
-
Caching: Node.js BFFs können Caching-Mechanismen implementieren (z. B. In-Memory oder Redis), um häufig angeforderte Daten zu speichern und so die Last auf Microservices weiter zu reduzieren und Antwortzeiten zu verbessern.
Anwendungsszenarien
Ein BFF ist in folgenden Szenarien besonders vorteilhaft:
- Komplexe UI-Ansichten: Wenn eine einzelne Benutzeroberfläche viele verschiedene Microservices benötigt.
- Mobile-spezifische Optimierungen: Anpassung von Antworten für mobile Geräte, die oft Bandbreitenbeschränkungen haben und kleinere, aggregierte Payloads bevorzugen.
- Integration von Altsystemen: Bei der Integration mit älteren, weniger flexiblen Systemen kann das BFF deren Komplexitäten abstrahieren und dem Frontend eine moderne API präsentieren.
- Mehrere Frontend-Clients: Wenn Sie eine Web-App, eine mobile App und möglicherweise ein Admin-Panel haben, die jeweils leicht unterschiedliche Daten oder Operationen benötigen, kann jeder seine eigene dedizierte BFF haben.
Fazit
Das Backend-for-Frontend (BFF)-Muster, insbesondere wenn es mit JavaScript und Node.js implementiert wird, bietet eine elegante und leistungsstarke Lösung für die Herausforderungen der Frontend-Entwicklung in einer Microservice-Welt. Als dedizierter Vermittler rationalisiert ein BFF die Datenaggregation, vereinfacht die Datentransformation und verbessert letztendlich sowohl die Entwicklerproduktivität als auch die Endbenutzererfahrung, indem es hochgradig optimierte Daten-Payloads liefert. Die Einführung einer BFF-Schicht ermöglicht es Ihrem Frontend, sich auf die Präsentation zu konzentrieren, während Ihre Backend-Microservices unabhängig bleiben und sich auf ihre Kerndomänen konzentrieren. Der Aufbau einer BFF-Schicht mit JavaScript vereinfacht die Frontend-Interaktion mit Ihren Microservices und liefert optimale Leistung und Entwicklererfahrung.