Einführung in Python-Microservices mit Nameko

Veröffentlicht: 2022-03-11

Einführung

Das Microservices-Architekturmuster ist ein Architekturstil, der aufgrund seiner Flexibilität und Belastbarkeit immer beliebter wird. Zusammen mit Technologien wie Kubernetes wird es einfacher als je zuvor, eine Anwendung mithilfe einer Microservices-Architektur zu booten.

Laut einem klassischen Artikel aus dem Blog von Martin Fowler kann der Microservices-Architekturstil wie folgt zusammengefasst werden:

Kurz gesagt, der Microservice-Architekturstil ist ein Ansatz zur Entwicklung einer einzelnen Anwendung als eine Suite kleiner Dienste, die jeweils in einem eigenen Prozess ausgeführt werden und mit einfachen Mechanismen kommunizieren, häufig einer HTTP-Ressourcen-API. Diese Services basieren auf Geschäftsfunktionen und können durch vollautomatische Bereitstellungsmaschinen unabhängig bereitgestellt werden.

Mit anderen Worten, eine Anwendung, die einer Microservices-Architektur folgt, besteht aus mehreren unabhängigen und dynamischen Diensten, die über ein Kommunikationsprotokoll miteinander kommunizieren. Es ist üblich, HTTP (und REST) ​​zu verwenden, aber wie wir sehen werden, können wir andere Arten von Kommunikationsprotokollen wie RPC (Remote Procedure Call) über AMQP (Advanced Message Queuing Protocol) verwenden.

Das Microservices-Muster kann als ein spezifischer Fall von SOA (serviceorientierte Architektur) betrachtet werden. In SOA ist es jedoch üblich, einen ESB (Enterprise Service Bus) zu verwenden, um die Kommunikation zwischen Diensten zu verwalten. ESBs sind in der Regel hochentwickelt und enthalten Funktionen für komplexes Nachrichten-Routing und die Anwendung von Geschäftsregeln. Bei Microservices ist es üblicher, einen alternativen Ansatz zu verwenden: „Smart Endpoints and Dumb Pipes“, was bedeutet, dass die Dienste selbst die gesamte Geschäftslogik und Komplexität enthalten sollten (hohe Kohäsion), aber die Verbindung zwischen den Diensten so einfach wie möglich sein sollte möglich (hohe Entkopplung), was bedeutet, dass ein Dienst nicht unbedingt wissen muss, welche anderen Dienste mit ihm kommunizieren werden. Dies ist eine auf architektonischer Ebene angewandte Trennung von Belangen.

Ein weiterer Aspekt von Microservices ist, dass es keine Durchsetzung dafür gibt, welche Technologien in jedem Service verwendet werden sollten. Sie sollten in der Lage sein, einen Dienst mit jedem Software-Stack zu schreiben, der mit den anderen Diensten kommunizieren kann. Jeder Dienst hat auch sein eigenes Lifecycle-Management. All dies bedeutet, dass es in einem Unternehmen möglich ist, dass Teams an separaten Diensten mit unterschiedlichen Technologien und sogar Managementmethoden arbeiten. Jedes Team wird sich mit Geschäftsfähigkeiten befassen und dabei helfen, eine agilere Organisation aufzubauen.

Python-Microservices

Unter Berücksichtigung dieser Konzepte konzentrieren wir uns in diesem Artikel auf die Erstellung einer Proof-of-Concept-Microservices-Anwendung mit Python. Dafür verwenden wir Nameko, ein Python-Microservices-Framework. RPC über AMQP ist integriert, sodass Sie problemlos zwischen Ihren Diensten kommunizieren können. Es hat auch eine einfache Schnittstelle für HTTP-Abfragen, die wir in diesem Tutorial verwenden werden. Zum Schreiben von Microservices, die einen HTTP-Endpunkt verfügbar machen, wird jedoch empfohlen, ein anderes Framework wie Flask zu verwenden. Um Nameko-Methoden über RPC mit Flask aufzurufen, können Sie Flask_nameko verwenden, einen Wrapper, der nur für die Zusammenarbeit von Flask mit Nameko entwickelt wurde.

Einstellen der Basisumgebung

Beginnen wir mit dem Ausführen des einfachstmöglichen Beispiels, das von der Nameko-Website extrahiert wurde, und erweitern Sie es für unsere Zwecke. Zunächst muss Docker installiert sein. Wir werden in unseren Beispielen Python 3 verwenden, stellen Sie also sicher, dass Sie es auch installiert haben. Erstellen Sie dann eine python virtualenv und führen Sie $ pip install nameko aus.

Um Nameko auszuführen, benötigen wir den RabbitMQ Message Broker. Es wird für die Kommunikation zwischen unseren Nameko-Diensten verantwortlich sein. Machen Sie sich jedoch keine Sorgen, da Sie keine weitere Abhängigkeit auf Ihrem Computer installieren müssen. Mit Docker können wir einfach ein vorkonfiguriertes Image herunterladen, ausführen und wenn wir fertig sind, einfach den Container stoppen. Keine Daemons, apt-get oder dnf install .

Python Microservices mit Nameko im Gespräch mit einem RabbitMQ-Broker

Starten Sie einen RabbitMQ-Container, indem Sie $ docker run -p 5672:5672 --hostname nameko-rabbitmq rabbitmq:3 (möglicherweise benötigen Sie dazu sudo). Dadurch wird ein Docker-Container mit der neuesten RabbitMQ-Version 3 gestartet und über den Standardport 5672 verfügbar gemacht.

Hallo Welt mit Microservices

Machen Sie weiter und erstellen Sie eine Datei namens hello.py mit folgendem Inhalt:

 from nameko.rpc import rpc class GreetingService: name = "greeting_service" @rpc def hello(self, name): return "Hello, {}!".format(name)

Nameko-Dienste sind Klassen. Diese Klassen legen Einstiegspunkte offen, die als Erweiterungen implementiert sind. Die integrierten Erweiterungen umfassen die Möglichkeit, Einstiegspunkte zu erstellen, die RPC-Methoden, Ereignis-Listener, HTTP-Endpunkte oder Timer darstellen. Es gibt auch Community-Erweiterungen, die verwendet werden können, um mit der PostgreSQL-Datenbank, Redis usw. zu interagieren. Es ist möglich, eigene Erweiterungen zu schreiben.

Lassen Sie uns fortfahren und unser Beispiel ausführen. Wenn Sie RabbitMQ auf dem Standardport ausgeführt haben, führen Sie einfach $ nameko run hello . Es findet RabbitMQ und stellt automatisch eine Verbindung her. Führen Sie dann zum Testen unseres Dienstes $ nameko shell in einem anderen Terminal aus. Dadurch wird eine interaktive Shell erstellt, die sich mit derselben RabbitMQ-Instanz verbindet. Das Tolle ist, dass Nameko durch die Verwendung von RPC über AMQP eine automatische Diensterkennung implementiert. Beim Aufruf einer RPC-Methode versucht nameko, den entsprechenden laufenden Dienst zu finden.

Zwei Nameko-Dienste, die über RabbitMQ RPC kommunizieren

Wenn Sie die Nameko-Shell ausführen, erhalten Sie ein spezielles Objekt namens n , das dem Namespace hinzugefügt wird. Dieses Objekt ermöglicht das Versenden von Ereignissen und das Ausführen von RPC-Aufrufen. Führen Sie für einen RPC-Aufruf an unseren Dienst Folgendes aus:

 > >> n.rpc.greetingservice.hello(name='world') 'Hello, world!'

Gleichzeitige Anrufe

Diese Dienstklassen werden in dem Moment instanziiert, in dem ein Aufruf getätigt wird, und zerstört, nachdem der Aufruf abgeschlossen ist. Daher sollten sie von Natur aus zustandslos sein, was bedeutet, dass Sie nicht versuchen sollten, zwischen Aufrufen irgendeinen Zustand im Objekt oder in der Klasse beizubehalten. Dies impliziert, dass die Dienste selbst zustandslos sein müssen. Unter der Annahme, dass alle Dienste zustandslos sind, kann Nameko die Parallelität durch die Verwendung von Eventlet-Greenthreads nutzen. Die instanziierten Dienste werden „Worker“ genannt, und es kann eine konfigurierte maximale Anzahl von Workern gleichzeitig ausgeführt werden.

Um die Nameko-Parallelität in der Praxis zu überprüfen, ändern Sie den Quellcode, indem Sie dem Prozeduraufruf einen Ruhezustand hinzufügen, bevor Sie die Antwort zurückgeben:

 from time import sleep from nameko.rpc import rpc class GreetingService: name = "greeting_service" @rpc def hello(self, name): sleep(5) return "Hello, {}!".format(name)

Wir verwenden sleep aus dem time , das nicht async-fähig ist. Wenn Sie unsere Dienste jedoch mit nameko run , werden Trigger-Erträge automatisch gepatcht, wenn Aufrufe wie sleep(5) blockiert werden.

Es wird jetzt erwartet, dass die Antwortzeit von einem Prozeduraufruf etwa 5 Sekunden dauern sollte. Wie wird sich jedoch das folgende Snippet verhalten, wenn wir es von der Nameko-Shell ausführen?

 res = [] for i in range(5): hello_res = n.rpc.greeting_service.hello.call_async(name=str(i)) res.append(hello_res) for hello_res in res: print(hello_res.result())

Nameko stellt für jeden RPC-Einstiegspunkt eine nicht blockierende call_async -Methode bereit, die ein Proxy-Antwortobjekt zurückgibt, das dann nach seinem Ergebnis abgefragt werden kann. Die result wird, wenn sie auf dem Antwort-Proxy aufgerufen wird, blockiert, bis die Antwort zurückgegeben wird.

Wie erwartet läuft dieses Beispiel in nur etwa fünf Sekunden. Jeder Arbeiter wird blockiert, während er darauf wartet, dass der sleep beendet wird, aber dies hält einen anderen Arbeiter nicht davon ab, damit zu beginnen. Ersetzen Sie diesen sleep -Aufruf beispielsweise durch einen nützlichen blockierenden E/A-Datenbankaufruf, und Sie erhalten einen extrem schnellen gleichzeitigen Dienst.

Wie bereits erläutert, erstellt Nameko Worker, wenn eine Methode aufgerufen wird. Die maximale Anzahl der Worker ist konfigurierbar. Standardmäßig ist diese Zahl auf 10 eingestellt. Sie können testen, ob Sie den range(5) im obigen Codeausschnitt beispielsweise in den Bereich (20) ändern. Dadurch wird die hello -Methode 20 Mal aufgerufen, die nun zehn Sekunden dauern sollte:

 > >> res = [] > >> for i in range(20): ... hello_res = n.rpc.greeting_service.hello.call_async(name=str(i)) ... res.append(hello_res) > >> for hellores in res: ... print(hello_res.result()) Hello, 0! Hello, 1! Hello, 2! Hello, 3! Hello, 4! Hello, 5! Hello, 6! Hello, 7! Hello, 8! Hello, 9! Hello, 10! Hello, 11! Hello, 12! Hello, 13! Hello, 14! Hello, 15! Hello, 16! Hello, 17! Hello, 18! Hello, 19!

Angenommen, Sie haben zu viele (mehr als 10) gleichzeitige Benutzer, die diese hello Methode aufrufen. Einige Benutzer warten länger als die erwarteten fünf Sekunden auf die Antwort. Eine Lösung bestand darin, die Anzahl der Werke zu erhöhen, indem die Standardeinstellungen beispielsweise mithilfe einer Konfigurationsdatei überschrieben wurden. Wenn Ihr Server jedoch mit diesen zehn Workern bereits am Limit ist, weil die aufgerufene Methode auf einige schwere Datenbankabfragen angewiesen ist, kann eine Erhöhung der Anzahl der Worker dazu führen, dass sich die Antwortzeit noch weiter verlängert.

Skalierung unseres Dienstes

Eine bessere Lösung ist die Verwendung von Nameko Microservices-Funktionen. Bisher haben wir nur einen Server (Ihren Computer) verwendet, auf dem eine Instanz von RabbitMQ und eine Instanz des Dienstes ausgeführt werden. In einer Produktionsumgebung sollten Sie die Anzahl der Knoten, auf denen der Dienst ausgeführt wird, der zu viele Aufrufe erhält, willkürlich erhöhen. Sie können auch einen RabbitMQ-Cluster erstellen, wenn Sie möchten, dass Ihr Nachrichtenbroker zuverlässiger ist.

Um eine Dienstskalierung zu simulieren, können wir einfach ein anderes Terminal öffnen und den Dienst wie zuvor mit $ nameko run hello . Dadurch wird eine weitere Dienstinstanz mit dem Potenzial gestartet, zehn weitere Worker auszuführen. Versuchen Sie nun, dieses Snippet erneut mit range(20) auszuführen. Es sollte jetzt wieder fünf Sekunden dauern, bis es läuft. Wenn mehr als eine Dienstinstanz ausgeführt wird, wird Nameko die RPC-Anforderungen zwischen den verfügbaren Instanzen verteilen.

Nameko wurde entwickelt, um diese Methodenaufrufe in einem Cluster robust zu handhaben. Um das zu testen, versuchen Sie, das Snipped auszuführen, und gehen Sie, bevor es fertig ist, zu einem der Terminals, auf denen der Nameko-Dienst ausgeführt wird, und drücken Sie zweimal Ctrl+C Dies würde den Host herunterfahren, ohne auf die Fertigstellung der Worker zu warten. Nameko wird die Anrufe einer anderen verfügbaren Dienstinstanz zuweisen.

In der Praxis würden Sie Docker verwenden, um Ihre Dienste zu containerisieren, wie wir später sehen werden, und ein Orchestrierungstool wie Kubernetes, um Ihre Knoten zu verwalten, auf denen der Dienst und andere Abhängigkeiten wie der Nachrichtenbroker ausgeführt werden. Bei richtiger Ausführung würden Sie mit Kubernetes Ihre Anwendung effektiv in ein robustes verteiltes System umwandeln, das gegen unerwartete Spitzen immun ist. Außerdem ermöglicht Kubernetes Bereitstellungen ohne Ausfallzeiten. Daher wirkt sich die Bereitstellung einer neuen Version eines Dienstes nicht auf die Verfügbarkeit Ihres Systems aus.

Es ist wichtig, Dienste unter Berücksichtigung einer gewissen Abwärtskompatibilität zu erstellen, da es in einer Produktionsumgebung vorkommen kann, dass mehrere verschiedene Versionen desselben Dienstes gleichzeitig ausgeführt werden, insbesondere während der Bereitstellung. Wenn Sie Kubernetes verwenden, werden während der Bereitstellung alle Container der alten Version nur beendet, wenn genügend neue Container ausgeführt werden.

Für Nameko ist es kein Problem, mehrere verschiedene Versionen desselben Dienstes gleichzeitig laufen zu lassen. Da die Aufrufe nach dem Round-Robin-Prinzip verteilt werden, können die Aufrufe alte oder neue Versionen durchlaufen. Um dies zu testen, lassen Sie ein Terminal mit unserem Dienst in der alten Version laufen und bearbeiten Sie das Dienstmodul so, dass es wie folgt aussieht:

 from time import sleep from nameko.rpc import rpc class GreetingService: name = "greeting_service" @rpc def hello(self, name): sleep(5) return "Hello, {}! (version 2)".format(name)

Wenn Sie diesen Dienst von einem anderen Terminal aus ausführen, werden die beiden Versionen gleichzeitig ausgeführt. Führen Sie jetzt unser Test-Snippet erneut aus und Sie werden sehen, dass beide Versionen angezeigt werden:

 > >> res = [] > >> for i in range(5): ... hello_res = n.rpc.greeting_service.hello.call_async(name=str(i)) ... res.append(hello_res) > >> for hellores in res: ... print(hello_res.result()) Hello, 0! Hello, 1! (version 2) Hello, 2! Hello, 3! (version 2) Hello, 4!

Arbeiten mit mehreren Instanzen

Jetzt wissen wir, wie man effektiv mit Nameko arbeitet und wie Skalierung funktioniert. Lassen Sie uns jetzt einen Schritt weiter gehen und ein weiteres Tool aus dem Docker-Ökosystem verwenden: docker-compose. Dies funktioniert, wenn Sie auf einem einzelnen Server bereitstellen, was definitiv nicht ideal ist, da Sie viele der Vorteile einer Microservices-Architektur nicht nutzen werden. Auch hier können Sie, wenn Sie eine geeignetere Infrastruktur wünschen, ein Orchestrierungstool wie Kubernetes verwenden, um ein verteiltes System von Containern zu verwalten. Also, fahren Sie fort und installieren Sie docker-compose.

Auch hier müssen wir nur eine RabbitMQ-Instanz bereitstellen und Nameko kümmert sich um den Rest, da alle Dienste auf diese RabbitMQ-Instanz zugreifen können. Der vollständige Quellcode für dieses Beispiel ist in diesem GitHub-Repository verfügbar.

Lassen Sie uns eine einfache Reiseanwendung erstellen, um die Fähigkeiten von Nameko zu testen. Diese Anwendung ermöglicht die Registrierung von Flughäfen und Reisen. Jeder Flughafen wird einfach als Name des Flughafens gespeichert, und die Reise speichert die IDs für die Start- und Zielflughäfen. Die Architektur unseres Systems sieht wie folgt aus:

Abbildung der Reiseanwendung

Idealerweise hätte jeder Microservice eine eigene Datenbankinstanz. Der Einfachheit halber habe ich jedoch eine einzige Redis-Datenbank erstellt, die sowohl von Trips- als auch von Airports-Microservices gemeinsam genutzt werden kann. Der Gateway-Microservice empfängt HTTP-Anforderungen über eine einfache REST-ähnliche API und verwendet RPC, um mit Airports und Trips zu kommunizieren.

Beginnen wir mit dem Gateway-Microservice. Seine Struktur ist einfach und sollte jedem, der von einem Framework wie Flask kommt, sehr vertraut sein. Wir definieren grundsätzlich zwei Endpunkte, die jeweils sowohl GET- als auch POST-Methoden zulassen:

 import json from nameko.rpc import RpcProxy from nameko.web.handlers import http class GatewayService: name = 'gateway' airports_rpc = RpcProxy('airports_service') trips_rpc = RpcProxy('trips_service') @http('GET', '/airport/<string:airport_id>') def get_airport(self, request, airport_id): airport = self.airports_rpc.get(airport_id) return json.dumps({'airport': airport}) @http('POST', '/airport') def post_airport(self, request): data = json.loads(request.get_data(as_text=True)) airport_id = self.airports_rpc.create(data['airport']) return airport_id @http('GET', '/trip/<string:trip_id>') def get_trip(self, request, trip_id): trip = self.trips_rpc.get(trip_id) return json.dumps({'trip': trip}) @http('POST', '/trip') def post_trip(self, request): data = json.loads(request.get_data(as_text=True)) trip_id = self.trips_rpc.create(data['airport_from'], data['airport_to']) return trip_id

Werfen wir nun einen Blick auf den Airports-Service. Wie erwartet werden zwei RPC-Methoden verfügbar gemacht. Die get -Methode fragt einfach die Redis-Datenbank ab und gibt den Flughafen für die angegebene ID zurück. Die create -Methode generiert eine zufällige ID, speichert die Flughafeninformationen und gibt die ID zurück:

 import uuid from nameko.rpc import rpc from nameko_redis import Redis class AirportsService: name = "airports_service" redis = Redis('development') @rpc def get(self, airport_id): airport = self.redis.get(airport_id) return airport @rpc def create(self, airport): airport_id = uuid.uuid4().hex self.redis.set(airport_id, airport) return airport_id

Beachten Sie, wie wir die Erweiterung nameko_redis . Sehen Sie sich die Liste der Community-Erweiterungen an. Erweiterungen werden so implementiert, dass Abhängigkeitsinjektion verwendet wird. Nameko kümmert sich um die Initiierung des eigentlichen Erweiterungsobjekts, das jeder Worker verwenden wird.

Es gibt keinen großen Unterschied zwischen den Microservices Airports und Trips. So würde der Trips-Microservice aussehen:

 import uuid from nameko.rpc import rpc from nameko_redis import Redis class AirportsService: name = "trips_service" redis = Redis('development') @rpc def get(self, trip_id): trip = self.redis.get(trip_id) return trip @rpc def create(self, airport_from_id, airport_to_id): trip_id = uuid.uuid4().hex self.redis.set(trip_id, { "from": airport_from_id, "to": airport_to_id }) return trip_id

Das Dockerfile für jeden Microservice ist ebenfalls sehr einfach. Die einzige Abhängigkeit ist nameko , und im Fall der Airports- und Trips-Dienste muss auch nameko-redis installiert werden. Diese Abhängigkeiten sind in der requirements.txt in jedem Dienst angegeben. Das Dockerfile für den Airports-Dienst sieht folgendermaßen aus:

 FROM python:3 RUN apt-get update && apt-get -y install netcat && apt-get clean WORKDIR /app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY config.yml ./ COPY run.sh ./ COPY airports.py ./ RUN chmod +x ./run.sh CMD ["./run.sh"]

Der einzige Unterschied zwischen diesem und dem Dockerfile für die anderen Dienste ist die Quelldatei (in diesem Fall airports.py ), die entsprechend geändert werden sollte.

Das Skript run.sh kümmert sich um das Warten, bis RabbitMQ und im Falle der Airports- und Trips-Dienste die Redis-Datenbank bereit ist. Das folgende Snippet zeigt den Inhalt von run.sh für Flughäfen. Auch hier ändern Sie für die anderen Dienste einfach entsprechend von aiports zu gateway oder trips :

 #!/bin/bash until nc -z ${RABBIT_HOST} ${RABBIT_PORT}; do echo "$(date) - waiting for rabbitmq..." sleep 1 done until nc -z ${REDIS_HOST} ${REDIS_PORT}; do echo "$(date) - waiting for redis..." sleep 1 done nameko run --config config.yml airports

Unsere Dienste sind jetzt betriebsbereit:

$ docker-compose up

Lassen Sie uns unser System testen. Führen Sie den Befehl aus:

 $ curl -i -d "{\"airport\": \"first_airport\"}" localhost:8000/airport HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Content-Length: 32 Date: Sun, 27 May 2018 05:05:53 GMT f2bddf0e506145f6ba0c28c247c54629

Diese letzte Zeile ist die generierte ID für unseren Flughafen. Um zu testen, ob es funktioniert, führen Sie Folgendes aus:

 $curl localhost:8000/airport/f2bddf0e506145f6ba0c28c247c54629 {"airport": "first_airport"} Great, now let's add another airport: $ curl -i -d "{\"airport\": \"second_airport\"}" localhost:8000/airport HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Content-Length: 32 Date: Sun, 27 May 2018 05:06:00 GMT 565000adcc774cfda8ca3a806baec6b5

Jetzt haben wir zwei Flughäfen, das reicht für eine Reise. Lassen Sie uns jetzt eine Reise erstellen:

 $ curl -i -d "{\"airport_from\": \"f2bddf0e506145f6ba0c28c247c54629\", \"airport_to\": \"565000adcc774cfda8ca3a806baec6b5\"}" localhost:8000/trip HTTP/1.1 200 OK Content-Type: text/plain; charset=utf-8 Content-Length: 32 Date: Sun, 27 May 2018 05:09:10 GMT 34ca60df07bc42e88501178c0b6b95e4

Wie zuvor stellt diese letzte Zeile die Reise-ID dar. Überprüfen wir, ob es richtig eingefügt wurde:

 $ curl localhost:8000/trip/34ca60df07bc42e88501178c0b6b95e4 {"trip": "{'from': 'f2bddf0e506145f6ba0c28c247c54629', 'to': '565000adcc774cfda8ca3a806baec6b5'}"}

Zusammenfassung

Wir haben gesehen, wie Nameko funktioniert, indem wir eine lokal laufende Instanz von RabbitMQ erstellt, eine Verbindung dazu hergestellt und mehrere Tests durchgeführt haben. Anschließend haben wir das gewonnene Wissen angewendet, um ein einfaches System mit einer Microservices-Architektur zu erstellen.

Obwohl es extrem einfach ist, kommt unser System dem, was eine produktionsbereite Bereitstellung aussehen würde, sehr nahe. Sie würden vorzugsweise ein anderes Framework verwenden, um HTTP-Anforderungen wie Falcon oder Flask zu verarbeiten. Beide sind großartige Optionen und können problemlos zum Erstellen anderer HTTP-basierter Mikrodienste verwendet werden, falls Sie beispielsweise Ihren Gateway-Dienst unterbrechen möchten. Flask hat den Vorteil, dass es bereits ein Plugin für die Interaktion mit Nameko hat, aber Sie können nameko-proxy direkt von jedem Framework aus verwenden.

Nameko ist auch sehr einfach zu testen. Wir haben das Testen hier der Einfachheit halber nicht behandelt, aber sehen Sie sich die Testdokumentation von Nameko an.

Mit all den beweglichen Teilen innerhalb einer Microservices-Architektur möchten Sie sicherstellen, dass Sie über ein robustes Protokollierungssystem verfügen. Um eines zu erstellen, siehe Python Logging: An In-Depth Tutorial von Toptaler und Python Developer: Son Nguyen Kim.