Microservices in JavaScript: Architektur und Praxis
Die Microservice-Architektur (MSA) hat sich zum De-facto-Standard für den Aufbau komplexer, skalierbarer und fehlertoleranter Anwendungen entwickelt. Im Gegensatz zu einem Monolithen, bei dem der gesamte Code in einem einzigen Prozess gebunden ist, unterteilen Microservices das System in kleine, unabhängige Dienste. JavaScript, insbesondere die Node.js-Plattform, eignet sich aufgrund seines ereignisgesteuerten Modells und des riesigen Ökosystems hervorragend für die Implementierung von Microservices. In diesem Artikel analysieren wir die wichtigsten Prinzipien der Architektur, schreiben mehrere funktionierende Dienste und betrachten praktische Aspekte ihrer Interaktion.
Grundlegende Prinzipien der Microservice-Architektur
Bevor wir Code schreiben, ist es wichtig zu verstehen, was einen Microservice zu einem "Microservice" macht. Die grundlegenden Prinzipien sind:
- Einzelverantwortung: Jeder Dienst löst eine bestimmte geschäftliche Aufgabe (z. B. nur Autorisierung oder nur Auftragsverwaltung).
- Unabhängigkeit: Dienste werden unabhängig voneinander entwickelt, getestet und bereitgestellt.
- Datenisolierung: Jeder Dienst hat seine eigene Datenbank. Es gibt kein gemeinsames Repository.
- Lockere Kopplung: Dienste kommunizieren über das Netzwerk (API), nicht über direkte Funktionsaufrufe.
- Fehlertoleranz: Der Ausfall eines Dienstes sollte nicht das gesamte System zum Absturz bringen.
Node.js ist aufgrund seiner nicht-blockierenden Natur und Asynchronität ideal für diese Aufgaben, was bei einer großen Anzahl von Netzwerkaufrufen zwischen Diensten entscheidend ist.
Praxis: Erstellen von zwei einfachen Microservices
Schreiben wir zwei einfache Dienste mit Express.js: User Service (Benutzerverwaltung) und Order Service (Auftragsverwaltung).
1. User Service
Erstellen Sie eine Datei user-service/server.js:
const express = require('express');const app = express();app.use(express.json());
const users = [{ id: 1, name: 'Alice', email: 'alice@example.com' }];
app.get('/users/:id', (req, res) => { const user = users.find(u => u.id === parseInt(req.params.id)); if (!user) return res.status(404).json({ error: 'User not found' }); res.json(user);});
app.listen(3001, () => console.log('User Service running on port 3001'));2. Order Service
Erstellen Sie eine Datei order-service/server.js:
const express = require('express');const app = express();app.use(express.json());
const orders = [ { id: 1, userId: 1, product: 'Laptop', amount: 1500 }];
app.get('/orders/user/:userId', (req, res) => { const userOrders = orders.filter(o => o.userId === parseInt(req.params.userId)); res.json(userOrders);});
app.listen(3002, () => console.log('Order Service running on port 3002'));Jetzt haben wir zwei unabhängige Dienste, die auf verschiedenen Ports laufen. Dies ist die Grundlage der Microservice-Architektur.
Interaktion zwischen Diensten: REST und Message Broker
Dienste müssen kommunizieren. Betrachten wir zwei Hauptansätze.
3.1 Synchrone Interaktion (REST-API)
Ein Dienst ruft direkt die API eines anderen Dienstes auf. Um beispielsweise Aufträge mit dem Benutzernamen abzurufen, erstellen wir ein API-Gateway oder einen Service-Composer.
// api-gateway.jsconst axios = require('axios');
app.get('/users/:id/orders', async (req, res) => { try { const userResponse = await axios.get(`http://localhost:3001/users/${req.params.id}`); const ordersResponse = await axios.get(`http://localhost:3002/orders/user/${req.params.id}`); res.json({ user: userResponse.data, orders: ordersResponse.data }); } catch (error) { res.status(