
Wenn es um die Ext JS-Bibliothek geht, muss man von Kennern eine Menge negativer Dinge hören: schwer, teuer, fehlerhaft. In der Regel hängen die meisten Probleme mit der Unfähigkeit zusammen, es zu kochen. Bei einem korrekt zusammengestellten Projekt mit Sencha Cmd und allen CSS-Bildern wiegt die Produktion in der 1-MB-Region, die mit demselben Winkel vergleichbar ist. Ja, und Pannen sind nicht viel mehr ...
Es ist möglich, dass Senchas Idee sich auf diesen Nachwuchs bezieht, aber selbst seine prinzipiellen Gegner geben zu, dass es schwierig ist, die beste Lösung für den Aufbau ernsthafter Intranetprojekte zu finden.
Meiner Meinung nach ist das Wertvollste in Ext JS nicht eine Sammlung von UI-Komponenten, sondern eine gute OOP-Architektur. Selbst unter Berücksichtigung der rasanten Entwicklung von JS in den letzten Jahren fehlen in nativen Klassen noch viele der notwendigen Dinge, die vor 7 Jahren in Ext JS implementiert wurden (Namespaces, Mixins, statische Eigenschaften, bequemes Aufrufen übergeordneter Methoden). Dies hat mich vor einigen Jahren dazu veranlasst, mit dem Start von Ext JS-Klassen im Backend zu experimentieren. Über die ersten ähnlichen Experimente habe ich bereits Beiträge auf Habré geschrieben. Dieser Artikel beschreibt eine neue Implementierung alter und eine Reihe neuer Ideen.
Bevor wir beginnen, konzentrieren wir uns auf die Frage: Was denken Sie, wo wird es ausgeführt und was macht das folgende Code-Snippet?
Ext.define('Module.message.model.Message', { .... ,async newMessage() { ......... this.fireEvent('newmessage', data); ...... } ... })
Dieser Code wird auf dem Server ausgeführt und verursacht das Ereignis "newmessage" in allen Instanzen der Klasse "Module.message.model.Message" auf allen mit dem Server verbundenen Client-Computern.
Um die Möglichkeiten der Verwendung von serverseitigem Ext JS zu veranschaulichen, analysieren wir ein einfaches Chat-Projekt. Wir werden uns nicht anmelden, nur wenn Sie den Benutzer eingeben, geben Sie einen Spitznamen ein. Sie können allgemeine oder private Nachrichten posten. Der Chat sollte in Echtzeit funktionieren. Wer möchte, kann diese ganze Wirtschaft sofort ausprobieren.
Installation
Zu Beginn benötigen wir nodejs 9+ und redis-server (es wird davon ausgegangen, dass sie bereits installiert sind).
git clone https://github.com/Kolbaskin/extjs-backend-example cd extjs-backend-example npm i
Wir starten den Server:
node server
Öffnen Sie im Browser die
localhost- Seite: 3000 / www / auth /
Geben Sie einen Spitznamen ein und drücken Sie die Eingabetaste.
Das Projekt ist eine Demo, daher gibt es keine Unterstützung für alte Browser (es gibt ES8-Designs). Verwenden Sie das neue Chrome oder FF.
Server
Lass uns in Ordnung gehen.
Servercode (server.js)
Wie Sie sehen, ist hier alles mehr oder weniger Standard für den Express-Server. Von Interesse ist die Aufnahme von Ext JS-Klassen, um die entsprechenden Routen zu bedienen:
app.use('/api/auth', Ext.create('Api.auth.Main')); app.use('/www/auth', Ext.create('Www.login.controller.Login'));
REST-API-Implementierung
Die Api.auth.Main-Klasse liefert Anforderungen an die REST-API (protected / rest / auth / Main.js).
Ext.define('Api.auth.Main', { extend: 'Api.Base',
HTML-Seitengenerierung mit XTemplate auf dem Server
Die zweite Klasse, Www.login.controller.Login, erstellt eine reguläre HTML-Seite mit einem Anmeldeformular (protected / www / login / controller / Login.js).
Ext.define('Www.login.controller.Login', {
Die Vorlagen verwenden die Standard-XTemplate (protected / www / login / view / login.tpl).
<h2>{pageTitle} (date: {[Ext.Date.format(values.date,'dmY')]})</h2> <form method="post"> <input name="name" placeholder="name"> <button type="submit">enter</button> </form>
Alles, was oben beschrieben wurde, ist ein völlig normaler Satz, wird ein akribischer Leser sagen, und dafür war es nicht nötig, diesen Garten mit der Übertragung von Ext JS auf den Server zu umzäunen. Daher fahren wir mit dem zweiten Teil des Artikels fort, der zeigt, wofür alles gedacht war.
Kunde
Lassen Sie uns die übliche Client-Ext-JS-Anwendung im statischen Verzeichnis erstellen. In diesem Beispiel habe ich bewusst nicht auf die Verwendung von cmd geachtet, sondern das bereits erstellte ext-all- und Standardthema übernommen. Versammlungsfragen sind ein separates Thema, das möglicherweise einen separaten Beitrag widmen wird.
Alles beginnt mit app.js.
Das Vorhandensein eines Web-Sockets ist ein entscheidender Punkt, mit dem Sie die unten beschriebene Magie implementieren können.
Das Layout der Elemente auf der Seite ist in der Klasse Admin.view.Viewport (static / app / view / Viewport.js) enthalten. Nichts interessantes da.
Die Hauptfunktionselemente (Benutzerliste, Nachrichtenleiste und Sendeformular) sind als separate Module implementiert.
Benutzerliste
Der einfache Algorithmus dieser Liste lautet wie folgt: Zum Zeitpunkt des Öffnens der Seite werden aktuelle Benutzer vom Server geladen. Wenn neue Benutzer eine Verbindung herstellen, generiert der Server ein Ereignis "Hinzufügen" in der Klasse "Module.users.model.UserModel". Wenn die Verbindung getrennt wird, wird in derselben Klasse das Ereignis "Entfernen" ausgelöst. Die Sache ist, dass das Ereignis auf der Serverseite ausgelöst wird und Sie es auf dem Client verfolgen können.
Nun, das Wichtigste zuerst. Auf der Clientseite jongliert Store mit Daten (statisch / app / modules / users / store / UsersStore.js).
Ext.define('Module.users.store.UsersStore', { extend: 'Ext.data.Store' ,autoLoad: true ,total: 0 ,constructor() {
Es gibt 2 interessante Punkte. Erstens in der Zeile "const data = warte auf this.dataModel. $ Read ();" Die Servermethode des Modells wird aufgerufen. Jetzt müssen Sie Ajax, Support-Protokolle usw. nicht mehr verwenden. Rufen Sie einfach die Servermethode als lokal auf. Gleichzeitig wird die Sicherheit nicht beeinträchtigt (mehr dazu weiter unten).
Zweitens können Sie mit der Standardkonstruktion von this.dataModel.on (...) Ereignisse verfolgen, die vom Server generiert werden.
Das Modell ist eine Brücke zwischen den Client- und Serverteilen der Anwendung. Es ist wie der Dualismus des Lichts - es implementiert die Eigenschaften sowohl des Frontends als auch des Backends. Schauen wir uns das Modell genau an.
Ext.define('Module.users.model.UserModel', { extend: 'Core.data.DataModel' ,testClientMethod() { ... } ,testGlobalMethod() { ... } ,privateServerMethod() { .... } ,async $read(params) {
Beachten Sie die Kommentare / * scope: server * / und / * scope: client * / - Diese Konstrukte sind Bezeichnungen für den Server, anhand dessen der Methodentyp bestimmt wird.
testClientMethod - Diese Methode wird ausschließlich auf dem Client ausgeführt und ist nur auf der Clientseite verfügbar.
testGlobalMethod - Diese Methode wird auf dem Client und auf dem Server ausgeführt und kann auf Client- und Serverseite verwendet werden.
privateServerMethod - Die Methode wird auf dem Server ausgeführt und kann nur auf dem Server aufgerufen werden.
$ read ist die interessanteste Methode, die nur auf der Serverseite ausgeführt wird. Sie können sie jedoch sowohl auf dem Client als auch auf dem Server aufrufen. Das Präfix $ stellt jede serverseitige Methode auf der Clientseite zur Verfügung.
Sie können die Clientverbindung und -trennung mithilfe eines Web-Sockets verfolgen. Für jede Benutzerverbindung wird eine Instanz der Base.wsClient-Klasse erstellt (protected / base / wsClient.js).
Ext.define('Base.wsClient', { extend: 'Core.WsClient'
Die fireEvent-Methode verfügt im Gegensatz zur Standardmethode über einen zusätzlichen Parameter, in dem übergeben wird, auf welchem Client das Ereignis ausgelöst werden soll. Es ist akzeptabel, eine einzelne Client-ID, ein Array von IDs oder die Zeichenfolge "all" zu übergeben. Im letzteren Fall wird das Ereignis auf allen verbundenen Clients ausgelöst. Ansonsten ist dies ein Standard-FireEvent.
Senden und Empfangen von Nachrichten
Der Formularcontroller (static / app / admin / modules / messages / view / FormController.js) ist für das Senden von Nachrichten verantwortlich.
Ext.define('Module.messages.view.FormController', { extend: 'Ext.app.ViewController' ,init(view) { this.view = view;
Die Nachricht wird nirgendwo auf dem Server gespeichert, das Ereignis "newmessage" wird einfach ausgelöst. Von Interesse ist der Aufruf "this.fireEvent ('newmessage', data.to, msg);", bei dem Client-IDs als Nachrichtenempfänger übergeben werden. Somit wird die Verteilung privater Nachrichten implementiert (statisch / app / admin / modules / messages / model / Model.js).
Ext.define('Module.messages.model.Model', { extend: 'Core.data.DataModel' ,async $newmessage(data) { const msg = { user: data.user, message: data.message } if(data.to && Ext.isArray(data.to) && data.to.length) { this.fireEvent('newmessage', data.to, msg); } else { this.fireEvent('newmessage', 'all', msg); } return true; } })
Wie bei Benutzern werden die Daten für die Nachrichtenliste vom Store gesteuert (statisch / app / admin / modules / messages / store / MessagesStore.js).
Ext.define('Module.messages.store.MessagesStore', { extend: 'Ext.data.Store', fields: ['user', 'message'], constructor() {
Im Allgemeinen ist dies alles, was in diesem Beispiel interessant ist.
Mögliche Fragen
Die Verfügbarkeit von Servermethoden auf dem Client ist natürlich gut, aber was ist mit der Sicherheit? Es stellt sich heraus, dass ein böser Hacker den Servercode sehen und versuchen kann, das Backend zu knacken?Nein, er wird keinen Erfolg haben. Zunächst werden alle Servermethoden aus dem Klassencode entfernt, wenn sie an den Client-Browser gesendet werden. Zu diesem Zweck sind Kommentare / Richtlinien / * Geltungsbereich vorgesehen: ... * /. Zweitens wird der Code der öffentlichsten serverseitigen Methode durch ein Zwischenkonstrukt ersetzt, das den Remote-Aufrufmechanismus auf der Clientseite implementiert.
Nochmals zur Sicherheit. Wenn Servermethoden auf dem Client aufgerufen werden können, kann ich eine solche Methode aufrufen? Und wenn dies eine Datenbankbereinigungsmethode ist?Vom Client aus können Sie nur Methoden aufrufen, deren Name das Präfix $ enthält. Für solche Methoden bestimmen Sie selbst die Logik von Überprüfungen und Zugriffen. Ein externer Benutzer hat keinen Zugriff auf Servermethoden ohne $, er wird sie nicht einmal sehen (siehe vorherige Antwort)
Es sieht so aus, als hätten Sie ein monolithisches System, in dem Client und Server untrennbar miteinander verbunden sind. Ist eine horizontale Skalierung möglich?Das System sieht zwar monolithisch aus, ist es aber nicht. Der Client und der Server können auf verschiedenen Computern "leben". Der Client kann auf jedem Webserver eines Drittanbieters (Nginx, Apache usw.) ausgeführt werden. Das Problem der Trennung von Client und Server kann vom automatischen Projekt-Builder sehr einfach gelöst werden (ich kann einen separaten Beitrag dazu schreiben). Um den internen Service-Messaging-Mechanismus zu implementieren, verwendet das System Warteschlangen (hierfür ist Redis erforderlich). Somit kann der Serverteil einfach horizontal skaliert werden, indem einfach neue Maschinen hinzugefügt werden.
Mit dem üblichen Entwicklungsansatz bietet das Backend in der Regel eine Reihe von APIs, mit denen Sie sich mit verschiedenen Clientanwendungen (Website, mobile Anwendung) verbinden können. In Ihrem Fall stellt sich heraus, dass nur ein in Ext JS geschriebener Client mit einem Backend arbeiten kann?Auf dem Server ist insbesondere in Modulmodellen eine bestimmte Geschäftslogik implementiert. Um über die REST-API darauf zugreifen zu können, reicht ein kleiner „Wrapper“ aus. Ein entsprechendes Beispiel wird im ersten Teil dieses Artikels vorgestellt.
Schlussfolgerungen
Wie Sie sehen können, ist es für eine komfortable Codierung ziemlich komplexer Anwendungen durchaus möglich, mit einer Bibliothek im Frontend und Backend auszukommen. Dies hat erhebliche Vorteile.
Beschleunigung des Entwicklungsprozesses. Jedes Teammitglied kann am Backend und Frontend arbeiten. Ausfallzeiten aus dem Grund "Ich warte darauf, dass diese API auf dem Server angezeigt wird" werden nicht relevant.
Weniger Code. Dieselben Codeabschnitte können auf dem Client und auf dem Server verwendet werden (Überprüfungen, Überprüfung usw.).
Die Wartung eines solchen Systems ist viel einfacher und billiger. Anstelle von zwei verschiedenen Programmierern kann das System einen (oder dieselben zwei, aber austauschbaren) unterstützen. Aus dem gleichen Grund sind auch die mit der Teamfluktuation verbundenen Risiken geringer.
Die Möglichkeit, sofort einsatzbereite Echtzeitsysteme zu erstellen.Verwendung eines einzigen Testsystems für Backend und Frontent.