Dart Service: Server Application Framework

Inhaltsverzeichnis




Ausbildung



Das letzte Mal, als wir damit endeten, platzierte ein statischer Webseiten-Dummy, der mit Flutter für das Web entwickelt wurde. Die Seite zeigt den Fortschritt der Entwicklung unseres Dienstes an, aber die Daten zu den Daten des Beginns der Entwicklung und Veröffentlichung mussten in der Anwendung fest codiert werden. Dadurch haben wir die Möglichkeit verloren, die Informationen auf der Seite zu ändern. Es ist Zeit, eine Datenserveranwendung zu entwickeln. Ein Diagramm aller Dienstanwendungen finden Sie im Artikel "Dienst in Dart-Sprache: Einführung, Backend-Infrastruktur" .



In diesem Artikel schreiben wir eine Anwendung mit dem Aqueduct- Framework, bewerten Sie die Leistung und den Ressourcenverbrauch in verschiedenen Modi, schreiben Sie ein Toolkit zum Kompilieren in eine native Anwendung für Windows und Linux, behandeln Sie Migrationen des Datenbankschemas für Domänenanwendungsklassen und veröffentlichen Sie unser Tool-Docker-Image sogar im öffentlichen DockerHub-Register.







Nützlichkeit




Aquädukt installieren



Beginnen wir mit der Installation von dart-sdk, einem Dart-Entwicklungskit. Sie können es mit dem hier vorgeschlagenen Paketmanager Ihres Betriebssystems installieren . Unter Windows ist jedoch standardmäßig kein Paketmanager auf Ihrem System installiert. Also nur:



  • Laden Sie das Archiv herunter und entpacken Sie es auf das Laufwerk C:
  • , , , . . « »



  • Path . dart , , C:\dart-sdk\bin
  • , dart pub ( dart)



    dart --version






    pub -v




  • , ,
  • aqueduct CLI (command line interface)



    pub global activate aqueduct






    aqueduct




Es ist theoretisch möglich, einen PostgreSQL- Datenbankserver auch lokal zu installieren . Mit Docker können wir diese Notwendigkeit jedoch vermeiden und die Entwicklungsumgebung der Laufzeit auf dem Server ähneln.



Anwendungsgenerierung



Öffnen wir also unseren Serverordner in VsCode



code c:/docs/dart_server


Für diejenigen, die den ersten und zweiten Artikel nicht gesehen haben , kann der Quellcode aus dem Guthub- Repository geklont werden :



git clone https://github.com/AndX2/dart_server.git
Lassen Sie uns eine Anwendungsvorlage erstellen:



aqueduct create data_app






Machen wir uns mit dem Inhalt der Projektvorlage vertraut:



  • README.md - Ein Hinweis, der beschreibt, wie Sie mit einem Aquäduktprojekt arbeiten, Tests ausführen, API-Dokumentation erstellen usw. Support-Datei.
  • pubspec.yaml — pub. , , , .





  • config.yaml config.src.yaml — . .
  • analysis_options.yaml — ( ). .
  • .travis.yml — (continuous Integration). .
  • pubspec.lock .packages — pub. — , , — ().
  • .dart_tool/package_config.json — , aqueduct CLI. .
  • bin/main.dart — (, ). ( ).





  • lib/channel.dartApplicationChannel — . Aqueduct CPU RAM. ( Dart isolate) () .



  • lib/data_app.dart — . (library) dart_app





  • test/ — . -, . Postman.


Aufbau



Die erste zu lösende Aufgabe ist das Einrichten der Anwendung beim Start. Aqueduct verfügt über einen integrierten Mechanismus zum Extrahieren von Parametern aus Konfigurationsdateien. Dieser Mechanismus ist jedoch nicht sehr praktisch, wenn er in einem Docker-Container ausgeführt wird. Wir werden anders handeln:



  • Übergeben wir die Liste der Variablen an das Container-Betriebssystem.
  • Beim Starten der Anwendung im Container lesen wir die Umgebungsvariablen des Betriebssystems und verwenden sie für die Erstkonfiguration.
  • Erstellen Sie eine Route zum Anzeigen aller Umgebungsvariablen der ausgeführten Anwendung über das Netzwerk (dies ist hilfreich, wenn Sie den Anwendungsstatus im Admin-Bereich anzeigen).


In der / lib Ordner erstellen mehrere Ordner und das erste Repository für den Zugriff auf Umgebungsvariablen:







EnvironmentRepository im Konstruktor liest Umgebungsvariablen aus dem Betriebssystem in Form einer Map <String, String> Wörterbuch und speichert sie in der privaten Variable _env . Fügen wir eine Methode hinzu, um alle Parameter in Form eines Wörterbuchs abzurufen : lib / service / EnvironmentService - die logische Komponente für den Zugriff auf EnvironmentRepository-Daten:















Abhängigkeitsspritze



Hier müssen Sie aufhören und sich mit Komponentenabhängigkeiten befassen:



  • Der Netzwerkcontroller benötigt eine Instanz des variablen Dienstes.
  • Der Dienst muss für die gesamte Anwendung eindeutig sein.
  • Um einen Service zu erstellen, müssen Sie zuerst eine Instanz des Variablen-Repositorys erstellen.


Wir werden diese Probleme mithilfe der GetIt- Bibliothek lösen . Verbinden Sie das erforderliche Paket mit pubspec.yaml :







Erstellen Sie eine Instanz des Injektorcontainers lib / di / di_container.dart und schreiben Sie eine Methode mit Registrierung des Repositorys und des Dienstes: Rufen Sie die DI-Containerinitialisierungsmethode in der Anwendungsvorbereitungsmethode auf:















Netzwerkschicht



lib / controller / ActuatorController - http-Netzwerkkomponente. Es enthält Methoden für den Zugriff auf die Servicedaten der Anwendung: Deklarieren Sie Routenhandler für Controller in lib / controller / Routes :















Erster Start



Zum Laufen benötigen Sie:



  • Packen Sie die Anwendung in ein Docker-Image.
  • Container zum Docker-Compose-Skript hinzufügen,
  • Konfigurieren Sie NGINX für Proxy-Anforderungen.


Erstellen Sie eine Docker-Datei im Anwendungsordner . Dies ist das Skript zum Erstellen und Ausführen des Images für Docker: Fügen Sie den Anwendungscontainer zum Skript docker-compose.yaml hinzu : Erstellen Sie die Datei data_app.env mit Konfigurationsvariablen für die Anwendung: Fügen Sie der NGINX-Debug-Konfiguration conf.dev.d / default.conf einen neuen Speicherort hinzu : Führen Sie das Debug aus Skript mit dem Pre-Build-Flag:



































docker-compose -f docker-compose.yaml -f docker-compose.dev.yaml up --build






Das Skript wurde erfolgreich ausgeführt, es gibt jedoch mehrere alarmierende Punkte:



  • Das offizielle Dart-Bild von Google ist 290 MB archiviert . Beim Auspacken nimmt es viel mehr Platz ein - 754 MB. Zeigen Sie eine Liste der Bilder und ihrer Größen an:



    docker images
  • Die Build- und JIT-Kompilierungszeit betrug mehr als 100 Sekunden. Zu viel, um eine App zum Verkauf anzubieten
  • Speicherverbrauch im Docker-Dashboard 300 MB unmittelbar nach dem Start



  • Bei einem Auslastungstest (nur Netzwerk-GET / API / Aktor / Anforderungen) liegt der Speicherverbrauch für eine Anwendung, die in einem Isolat ausgeführt wird, im Bereich von 350 bis 390 MB





Vermutlich reichen die Ressourcen unseres Budget-VPS nicht aus, um eine so ressourcenintensive Anwendung auszuführen. Lass uns das Prüfen:



  • Erstellen Sie auf dem Server einen Ordner für die neue Version der Anwendung und kopieren Sie den Inhalt des Projekts



    ssh root@dartservice.ru "mkdir -p /opt/srv_2" && scp -r ./* root@91.230.60.120:/opt/srv_2/
  • Jetzt müssen Sie das Webseitenprojekt von / opt / srv_1 / public / und den gesamten Inhalt des Ordners / opt / srv_1 / sertbot / in diesen Ordner übertragen (er enthält SSL-Zertifikate für NGINX und Let's verschlüsseln Bot-Protokolle) und den Schlüssel von / opt kopieren / srv_1 / dhparam /
  • Starten Sie den Serverressourcenmonitor in einer separaten Konsole



    htop


  • Führen Sie das Docker-Compose-Skript im Ordner / opt / srv_2 / aus



    docker-compose up --build -d
  • So sieht die Anwendungserstellung vor dem Start aus:





  • Und so - in der Arbeit:







    Von den verfügbaren 1 GB RAM verbraucht unsere Anwendung 1,5 GB, um die fehlende Auslagerungsdatei zu "belegen". Ja, die Anwendung wurde gestartet, aber es handelt sich nicht um eine Ladekapazität.
  • Beenden wir das Skript:



    docker-compose down


AOT



Wir haben drei Aufgaben zu lösen:



  • Reduzieren Sie den RAM-Verbrauch durch Dart-Anwendung,
  • Startzeit reduzieren,
  • Reduzieren Sie die Größe des Docker-Containers der Anwendung.


Die Lösung besteht darin, Dart zur Laufzeit aufzugeben. Seit Version 2.6 unterstützen Dart-Anwendungen die Kompilierung in nativen ausführbaren Code . Aqueduct unterstützt die Kompilierung ab Version 4.0.0-b1.



Beginnen wir mit dem lokalen Entfernen der Aquädukt-CLI:



pub global deactivate aqueduct


Installieren Sie die neue Version:



pub global activate aqueduct 4.0.0-b1


Lassen Sie uns die Abhängigkeiten in pubspec.yaml erhöhen: Erstellen wir die







native Anwendung:



aqueduct build


Das Ergebnis ist eine einzelne Datei data_app.aot mit einer Größe von ca. 6 MB . Sie können diese Anwendung sofort mit folgenden Parametern starten:



data_app.aot --port 8080 --isolates 2


Der Speicherverbrauch unmittelbar nach dem Start beträgt weniger als 10 MB.



Mal sehen unter Last. Testparameter: Netzwerk-GET / Aktor-Anforderungen, 100 Threads mit maximal verfügbarer Geschwindigkeit, 10 Minuten. Ergebnis:







Gesamt: Durchschnittsgeschwindigkeit - 13.000 Anforderungen pro Sekunde für einen 1,4-kV-JSON-Antworttext, durchschnittliche Antwortzeit - 7 ms, Speicherverbrauch (für zwei Instanzen) 42 MB. Es gibt keine Fehler.



Wenn wir den Test mit sechs Instanzen der Anwendung wiederholen, steigt die durchschnittliche Geschwindigkeit natürlich auf 19 k / s, aber die Auslastung des Prozessors erreicht 45%, wenn der Speicherverbrauch 64 MB beträgt.

Dies ist ein hervorragendes Ergebnis.



Behälterverpackung



Hier werden wir vor einer weiteren Schwierigkeit stehen: Wir können nur eine Dart-Anwendung in eine native für das aktuelle Betriebssystem kompilieren. In meinem Fall ist dies Windows 10 x64. In einem Docker-Container würde ich natürlich eine der Linux-Distributionen bevorzugen - zum Beispiel Ubuntu 20.10.



Die Lösung hier ist ein Docker-Zwischenständer, der nur zum Erstellen nativer Anwendungen für Ubuntu verwendet wird. Schreiben wir es / dart2native / Dockerfile : Jetzt bauen wir es in ein Docker-Image mit dem Namen aqueduct_builder : 4.0.0-b1 ein und überschreiben die alten Versionen, falls vorhanden:











docker build --pull --rm -f "dart2native\Dockerfile" -t aqueduct_builder:4.0.0-b1 "dart2native"


Lass uns das Prüfen:



docker images






Schreiben wir ein Build-Skript für die native Anwendung docker-compose.dev.build.yaml : Führen Sie das Build-Skript aus:











docker-compose -f docker-compose.dev.build.yaml up






Die für Ubuntu kompilierte Datei data_app.aot benötigt bereits 9 MB. Verwendet beim Start 19 MB RAM (für zwei Instanzen). Lassen Sie uns lokale Lasttests unter den gleichen Bedingungen durchführen, jedoch in einem Container mit NGINX-Proxy (GET, 100 Threads):







Durchschnittlich 5,3.000 Anforderungen pro Sekunde. Gleichzeitig überschritt der RAM-Verbrauch 55 MB nicht. Die Bildgröße hat sich im Vergleich zum installierten Pfeil und Aquädukt von 840 MB auf 74 MB auf der Festplatte verringert.



Schreiben wir ein neues Skript docker-compose.aot.yaml zum Starten der Anwendung. Dazu ersetzen wir den Beschreibungsblock data_app, indem wir das Basis-Image "leer" Ubuntu: 20.10 installieren. Hängen wir die Assembly-Datei ein und ändern den Startbefehl:







Lassen Sie uns noch ein Serviceproblem lösen: Tatsächlich ist das Docker-Build-Image mit installiertem Dart und Aquädukt ein ziemlich wiederverwendbares Werkzeug. Es ist sinnvoll, es in ein öffentliches Register hochzuladen und als fertiges herunterladbares Bild zu verbinden. Dafür braucht man:



  • Registrieren Sie sich mit einem öffentlichen Register wie DockerHub ,
  • Melden Sie sich lokal mit demselben Login an



    docker login 
  • Benennen Sie das hochgeladene Bild mit dem Schema login / title: tag um



    docker image tag a365ac7f5bbb andx2/aqueduct:4.0.0-b1
  • Bild in Register entladen



    docker push andx2/aqueduct:4.0.0-b1


    https://hub.docker.com/repository/docker/andx2/aqueduct/general



Jetzt können wir unser Build-Skript so ändern, dass es das öffentliche Image verwendet







Datenbankverbindung



Aqueduct verfügt bereits über ein integriertes ORM für die Arbeit mit einer PostgreSQL-Datenbank. Um es zu benutzen, benötigen Sie:



  • Erstellen Sie Domänenobjekte, die Datensätze in der Datenbank beschreiben.
  • . : , , . Aqueduct , , ManagedObject ( ), , . .
  • . , , .
  • , aqueduct, , seed() — - .
  • aqueduct CLI.


Beginnen wir damit, einen neuen Docker-Container mit der PostgreSQL-Datenbank im Skript docker-compose.aot.yaml zu verbinden. Bereites Image basierend auf Linux Alpine ("kompakte" Version von Linux für eingebettete Anwendungen): Hier müssen Sie auf die Umgebungsvariablendatei data_db.env achten . Tatsache ist, dass das Image vorkonfiguriert ist, um diese Variablen als Benutzername, Host, Port und Zugriffskennwort zu verwenden. Fügen wir der Datei folgende Variablen hinzu: Die Werte werden bedingt angegeben. Wir werden auch den Host-Ordner ./data_db/ in einen Container zum Speichern von Datenbankdaten einbinden . Fügen Sie als Nächstes in der Anwendung data_app die Klasse / service / DbHelper hinzu , um mithilfe von Umgebungsvariablen eine Verbindung zur Datenbank herzustellen :





























Erstellen Sie ein ORM-verwaltetes Domänenobjekt, um die Clientanwendungseinstellungen abzurufen: Fügen Sie ein Repository und einen Dienst hinzu, um Einstellungen hinzuzufügen und die aktuelle Version abzurufen : Netzwerkcontroller: Registrieren Sie neue Komponenten im DI-Container: Fügen Sie dem Router einen neuen Controller und Endpunkt hinzu: Jetzt generieren wir eine Datenbankmigrationsdatei. Lassen Sie uns ausführen:















































aqueduct db generate
Das Ergebnis ist die Erstellung von Migrationsdateien im Projektordner: Jetzt müssen Sie das Dienstproblem lösen: Migrationen müssen von einem System mit Aquädukt (und Dart) auf eine Datenbank angewendet werden, die in einem Container ausgeführt wird, und dies muss sowohl während der lokalen Entwicklung als auch auf dem Server erfolgen. In diesem Fall verwenden wir das zuvor erstellte und veröffentlichte Image für die AOT-Assembly. Schreiben wir das entsprechende Docker-Compose-Datenbankmigrationsskript: Ein interessantes Detail ist die Datenbankverbindungszeichenfolge. Wenn Sie das Skript ausführen, können Sie eine Datei mit Umgebungsvariablen als Argument übergeben und diese Variablen dann zum Ersetzen im Skript verwenden:























docker-compose -f docker-compose.migrations.yaml --env-file=./data_app.env --compatibility up --abort-on-container-exit


Achten wir auch auf die Startflaggen:



  • --kompatibilität - Kompatibilität mit Docker-Compose-Versionen von 2.x-Skripten. Auf diese Weise können Bereitstellungsoptionen verwendet werden, um die Ressourcennutzung durch den Container einzuschränken, die in Version 3.x ignoriert werden. Wir haben den RAM-Verbrauch auf 200 MB und die CPU-Auslastung auf 50% begrenzt.
  • --abort-on-container-exit - Dieses Flag setzt den Skriptausführungsmodus so, dass alle anderen beendet werden, wenn einer der Skriptcontainer stoppt. Wenn der Befehl zum Migrieren des Datenbankschemas ausgeführt wird und der Container mit Aquädukt stoppt, beendet Docker-Compose daher auch den Datenbankcontainer.


Veröffentlichung



Um die Veröffentlichung der Anwendung vorzubereiten, müssen Sie:



  • data_app.env data_db.env. , POSTGRES_PASSWORD=postgres_password
  • docker-compose.aot.yaml docker-compose.yaml.
  • /api/actuator. .


Kopieren Sie den Anwendungsordner ./data_app/ auf den Server . Ein wichtiger Punkt hierbei ist der Schalter -p (Kopieren unter Beibehaltung der Dateiattribute) im Befehl copy. Ich möchte Sie daran erinnern, dass wir beim Erstellen der nativen Anwendung die Ausführungsrechte für die Datei data_app.aot festlegen :



scp -rp ./data_app root@dartservice.ru:/opt/srv_1


Kopieren wir auch:



  • Geänderte NGINX-Konfiguration ./conf.d/default.conf
  • Start- und Migrationsskripte docker-compose.yaml , docker-compose.migrations.yaml
  • Dateien mit den Umgebungsvariablen data_app.env und data_db.env


Fügen Sie den Ordner / opt / srv_1 / data_db auf dem Server hinzu . Dies ist das Host-Dateisystem-Volume, das im Datenbankcontainer bereitgestellt werden soll. Alle PostgreSQL-Daten werden hier gespeichert.



mkdir /opt/srv_2/data_db


Führen wir das Skript zum Migrieren des Datenbankschemas aus:



docker-compose -f docker-compose.migrations.yaml --env-file=./data_app.env up --abort-on-container-exit


Lassen Sie uns das Anwendungsskript ausführen:



docker-compose up -d


-> Quellcode Github



Anstelle einer Schlussfolgerung



Das Framework für die Backend-Anwendungen ist fertig. Im nächsten Artikel werden wir darauf basierend eine neue Anwendung zur Autorisierung von Dienstnutzern schreiben. Dazu verwenden wir die oAuth2-Spezifikation und integrieren sie in VK und Github.



All Articles