Les sites Web modernes récupèrent généralement des données à partir d'un certain nombre d'emplacements différents, y compris des bases de données et des API tierces. Par exemple, lors de l'authentification d'un utilisateur, un site Web peut rechercher l'enregistrement utilisateur dans la base de données, puis l'embellir avec des données provenant de certains services externes via des appels d'API. La minimisation des appels coûteux à ces sources de données, tels que l'accès au disque pour les requêtes de base de données et les allers-retours Internet pour les appels d'API, est essentielle pour maintenir un site rapide et réactif. La mise en cache des données est une technique d'optimisation courante utilisée pour y parvenir.
Les processus stockent leurs données de travail en mémoire. Si un serveur Web s'exécute dans un seul processus (tel que Node.js / Express), ces données peuvent facilement être mises en cache à l'aide d'un cache mémoire exécuté dans le même processus. Cependant, les serveurs Web à charge équilibrée couvrent plusieurs processus et, même lorsque vous travaillez avec un seul processus, vous souhaiterez peut-être que le cache persiste au redémarrage du serveur. Cela nécessite une solution de mise en cache hors processus telle que Redis, ce qui signifie que les données doivent être sérialisées d'une manière ou d'une autre et désérialisées lorsqu'elles sont lues à partir du cache.
La sérialisation et la désérialisation sont relativement simples à réaliser dans des langages à typage statique tels que C #. Cependant, la nature dynamique de JavaScript rend le problème un peu plus délicat. Alors qu'ECMAScript 6 (ES6) a introduit les classes, les champs de ces classes (et leurs types) ne sont pas définis tant qu'ils ne sont pas initialisés - ce qui peut ne pas être le cas lorsque la classe est instanciée - et les types de retour de champs et de fonctions ne sont pas définis du tout dans le schéma. De plus, la structure de la classe peut facilement être modifiée au moment de l'exécution - des champs peuvent être ajoutés ou supprimés, des types peuvent être modifiés, etc. Bien que cela soit possible en utilisant la réflexion en C #, la réflexion représente les «arts sombres» de ce langage, et les développeurs s'attendent à ce qu'il brise les fonctionnalités.
J'ai été confronté à ce problème au travail il y a quelques années lorsque je travaillais dans l'équipe principale d'ApeeScape. Nous construisions un tableau de bord agile pour nos équipes, qui devait être rapide; sinon, les développeurs et les propriétaires de produits ne l'utiliseront pas. Nous avons extrait des données d'un certain nombre de sources: notre système de suivi du travail, notre outil de gestion de projet et une base de données. Le site a été construit en Node.js / Express, et nous avions un cache mémoire pour minimiser les appels à ces sources de données. Cependant, notre processus de développement rapide et itératif nous a permis de déployer (et donc de redémarrer) plusieurs fois par jour, invalidant le cache et perdant ainsi bon nombre de ses avantages.
Une solution évidente était un cache hors processus tel que Redis . Cependant, après quelques recherches, j'ai trouvé qu'il n'existait pas de bonne bibliothèque de sérialisation pour JavaScript. Les méthodes intégrées JSON.stringify / JSON.parse renvoient des données du type d'objet, perdant toutes les fonctions sur les prototypes des classes d'origine. Cela signifiait que les objets désérialisés ne pouvaient pas simplement être utilisés «en place» dans notre application, ce qui nécessiterait donc une refactorisation considérable pour fonctionner avec une conception alternative.
Afin de prendre en charge la sérialisation et la désérialisation de données arbitraires en JavaScript, avec les représentations désérialisées et les originaux utilisables de manière interchangeable, nous avions besoin d'une bibliothèque de sérialisation avec les propriétés suivantes:
Pour combler cette lacune, j'ai décidé d'écrire Tanagra.js , une bibliothèque de sérialisation à usage général pour JavaScript. Le nom de la bibliothèque fait référence à l'un de mes épisodes préférés de Star Trek: la nouvelle génération , où l'équipage du Entreprise doit apprendre à communiquer avec une mystérieuse race extraterrestre dont la langue est inintelligible. Cette bibliothèque de sérialisation prend en charge les formats de données courants pour éviter de tels problèmes.
Tanagra.js est conçu pour être simple et léger, et il prend actuellement en charge Node.js (il n'a pas été testé dans le navigateur, mais en théorie, cela devrait fonctionner) et les classes ES6 (y compris Maps). L'implémentation principale prend en charge JSON et une version expérimentale prend en charge les tampons de protocole Google. La bibliothèque ne nécessite que du JavaScript standard (actuellement testé avec ES6 et Node.js), sans dépendance aux fonctionnalités expérimentales, Babel transpiling, ou Manuscrit .
Les classes sérialisables sont marquées comme telles avec un appel de méthode lorsque la classe est exportée:
module.exports = serializable(Foo, myUniqueSerialisationKey)
La méthode renvoie un Procuration à la classe, qui intercepte le constructeur et injecte un identifiant unique. (S'il n'est pas spécifié, la valeur par défaut est le nom de la classe.) Cette clé est sérialisée avec le reste des données et la classe l'expose également en tant que champ statique. Si la classe contient des types imbriqués (c'est-à-dire des membres dont les types nécessitent une sérialisation), ils sont également spécifiés dans l'appel de méthode:
module.exports = serializable(Foo, [Bar, Baz], myUniqueSerialisationKey)
(Les types imbriqués pour les versions précédentes de la classe peuvent également être spécifiés de la même manière, de sorte que, par exemple, si vous sérialisez un Foo1, il puisse être désérialisé en un Foo2.)
Lors de la sérialisation, la bibliothèque crée de manière récursive une carte globale des clés vers les classes et l'utilise lors de la désérialisation. (N'oubliez pas que la clé est sérialisée avec le reste des données.) Afin de connaître le type de la classe «de premier niveau», la bibliothèque exige que cela soit spécifié dans l'appel de désérialisation:
const foo = decodeEntity(serializedFoo, Foo)
Une bibliothèque expérimentale de mappage automatique parcourt l'arborescence des modules et génère les mappages à partir des noms de classe, mais cela ne fonctionne que pour les classes nommées de manière unique.
Le projet est divisé en plusieurs modules:
Notez que la bibliothèque utilise l'orthographe américaine.
L'exemple suivant déclare une classe sérialisable et utilise le module tanagra-json pour la sérialiser / désérialiser:
const serializable = require('tanagra-core').serializable class Foo { constructor(bar, baz1, baz2, fooBar1, fooBar2) { this.someNumber = 123 this.someString = 'hello, world!' this.bar = bar // a complex object with a prototype this.bazArray = [baz1, baz2] this.fooBarMap = new Map([ ['a', fooBar1], ['b', fooBar2] ]) } } // Mark class `Foo` as serializable and containing sub-types `Bar`, `Baz` and `FooBar` module.exports = serializable(Foo, [Bar, Baz, FooBar]) ... const json = require('tanagra-json') json.init() // or: // require('tanagra-protobuf') // await json.init() const foo = new Foo(bar, baz) const encoded = json.encodeEntity(foo) ... const decoded = json.decodeEntity(encoded, Foo)
J'ai comparé les performances des deux sérialiseurs (le JSON sérialiseur et expérimental protobufs sérialiseur) avec un contrôle (natif JSON.parse et JSON.stringify). J'ai mené un total de 10 essais avec chacun.
J'ai testé ceci sur mon 2017 Dell XPS15 ordinateur portable avec 32 Go de mémoire, exécutant Ubuntu 17.10.
J'ai sérialisé l'objet imbriqué suivant:
foo: { 'string': 'Hello foo', 'number': 123123, 'bars': [ { 'string': 'Complex Bar 1', 'date': '2019-01-09T18:22:25.663Z', 'baz': { 'string': 'Simple Baz', 'number': 456456, 'map': Map { 'a' => 1, 'b' => 2, 'c' => 2 } } }, { 'string': 'Complex Bar 2', 'date': '2019-01-09T18:22:25.663Z', 'baz': { 'string': 'Simple Baz', 'number': 456456, 'map': Map { 'a' => 1, 'b' => 2, 'c' => 2 } } } ], 'bazs': Map { 'baz1' => Baz { string: 'baz1', number: 111, map: Map { 'a' => 1, 'b' => 2, 'c' => 2 } }, 'baz2' => Baz { string: 'baz2', number: 222, map: Map { 'a' => 1, 'b' => 2, 'c' => 2 } }, 'baz3' => Baz { string: 'baz3', number: 333, map: Map { 'a' => 1, 'b' => 2, 'c' => 2 } } }, }
Méthode de sérialisation | Ave. inc. premier essai (ms) | StDev. inc. premier essai (ms) | Ave. ex. premier essai (ms) | StDev. ex. premier essai (ms) |
JSON | 0,115 | 0,0903 | 0,0879 | 0,0256 |
Google Protobufs | 2,00 | 2 748 | 1.13 | 0,278 |
Groupe de contrôle | 0,0155 | 0,00726 | 0,0139 | 0,00570 |
Lis
Méthode de sérialisation | Ave. inc. premier essai (ms) | StDev. inc. premier essai (ms) | Ave. ex. premier essai (ms) | StDev. ex. premier essai (ms) |
JSON | 0,133 | 0,102 | 0,104 | 0,0429 |
Google Protobufs | 2,62 | 1.12 | 2,28 | 0,364 |
Groupe de contrôle | 0,0135 | 0,00729 | 0,0115 | 0,00390 |
La JSON le sérialiseur est environ 6 à 7 fois plus lent que la sérialisation native. L'expérimental protobufs le sérialiseur est environ 13 fois plus lent que le JSON sérialiseur, ou 100 fois plus lent que la sérialisation native.
En outre, la mise en cache interne des informations de schéma / structure dans chaque sérialiseur a clairement un effet sur les performances. Pour le sérialiseur JSON, la première écriture est environ quatre fois plus lente que la moyenne. Pour le sérialiseur protobuf, il est neuf fois plus lent. Ainsi, l'écriture d'objets dont les métadonnées ont déjà été mises en cache est beaucoup plus rapide dans l'une ou l'autre des bibliothèques.
Le même effet a été observé pour les lectures. Pour la bibliothèque JSON, la première lecture est environ quatre fois plus lente que la moyenne, et pour la bibliothèque protobuf, elle est environ deux fois et demie plus lente.
Les problèmes de performances du sérialiseur protobuf signifient qu'il est encore au stade expérimental, et je le recommanderais uniquement si vous avez besoin du format pour une raison quelconque. Cependant, cela vaut la peine d'investir du temps, car le format est beaucoup plus laconique que JSON, et donc meilleur pour l'envoi par fil. Stack Exchange utilise le format pour sa mise en cache interne.
Le sérialiseur JSON est clairement beaucoup plus performant mais toujours nettement plus lent que l'implémentation native. Pour les petites arborescences d'objets, cette différence n'est pas significative (quelques millisecondes en plus d'une requête de 50 ms ne détruiront pas les performances de votre site), mais cela pourrait devenir un problème pour les arborescences d'objets extrêmement grandes, et c'est l'une de mes priorités de développement.
La bibliothèque est encore en phase bêta. Le sérialiseur JSON est raisonnablement bien testé et stable. Voici la feuille de route pour les prochains mois:
Je ne connais aucune autre bibliothèque JavaScript qui prend en charge la sérialisation de données d'objets complexes et imbriqués et la désérialisation vers son type d'origine. Si vous mettez en œuvre une fonctionnalité qui bénéficierait de la bibliothèque, veuillez l'essayer, prendre contact avec vos commentaires et envisager de contribuer.
Page d'accueil du projet
Dépôt GitHub
combien y a-t-il de principes de gestalt
En général, les objets ne sont pas stockés. Ils sont instanciés en cas de besoin, utilisés dans le traitement, puis supprimés de la mémoire lorsqu'ils ne sont plus nécessaires. Si nous devons utiliser temporairement des données ailleurs, nous sérialisons et désérialisons ces données dans une autre structure.
Un objet est un morceau de code qui encapsule une structure, ainsi que des opérations qui peuvent être effectuées sur cette structure. Généralement, c'est la même chose qu'un objet dans n'importe quel langage de programmation orienté objet.
Un objet de données est un morceau de code qui contient temporairement des données d'un magasin de données afin qu'il puisse être lu ou traité dans une application. Sauf s'il existe un moyen de conserver ces données en mémoire, elles sont réécrites ou non conservées lorsque l'objet sort de la portée ou n'est pas nécessaire.
La sérialisation nous permet de conserver les données à utiliser dans le processus en cours d'exécution ou dans d'autres processus si nécessaire. Nous stockons les données, puis les désérialisons lorsqu'elles sont utilisées ailleurs.