{"id":260820,"date":"2022-06-14T10:27:49","date_gmt":"2022-06-14T09:27:49","guid":{"rendered":"https:\/\/blog.jetbrains.com\/fleet\/2022\/06\/fleet-below-deck-part-iii-state-management\/"},"modified":"2022-07-06T18:50:55","modified_gmt":"2022-07-06T17:50:55","slug":"dans-les-coulisses-de-fleet-gestion-d-etats","status":"publish","type":"fleet","link":"https:\/\/blog.jetbrains.com\/fr\/fleet\/2022\/06\/dans-les-coulisses-de-fleet-gestion-d-etats\/","title":{"rendered":"Dans les coulisses de Fleet, Partie III &#8211; Gestion d&#8217;\u00e9tats"},"content":{"rendered":"<p>Cet article fait partie d&#8217;une s\u00e9rie consacr\u00e9e \u00e0 la conception et au fonctionnement de Fleet, notre IDE nouvelle g\u00e9n\u00e9ration.<\/p>\n<p>\u00a0<\/p>\n<ul>\n<li>Partie I \u2013 <a href=\"https:\/\/blog.jetbrains.com\/fleet\/2022\/01\/fleet-below-deck-part-i-architecture-overview\/\">Vue d&#8217;ensemble de l&#8217;architecture<\/a><\/li>\n<li>Partie II \u2013 <a href=\"https:\/\/blog.jetbrains.com\/fleet\/2022\/02\/fleet-below-deck-part-ii-breaking-down-the-editor\/\">Pr\u00e9sentation d\u00e9taill\u00e9e de l&#8217;\u00e9diteur<\/a><\/li>\n<li>Partie III \u2013 Gestion d&#8217;\u00e9tats<\/li>\n<\/ul>\n<p>Pr\u00e9c\u00e9demment, nous avons abord\u00e9 les sujets de l&#8217;architecture de Fleet et des algorithmes et structures de donn\u00e9es utilis\u00e9s dans l&#8217;\u00e9diteur. Dans ce nouvel article, nous allons commencer \u00e0 examiner l&#8217;approche adopt\u00e9e pour impl\u00e9menter la gestion des \u00e9tats. Il s&#8217;agit d&#8217;un sujet complexe, que nous d\u00e9velopperons en plusieurs fois. Pour le moment, nous allons d\u00e9buter avec la repr\u00e9sentation et le stockage des \u00e9l\u00e9ments de l&#8217;\u00e9tat de l&#8217;application. Nous verrons plus en d\u00e9tail dans un prochain article quels sont les m\u00e9canismes transactionnels de la gestion des \u00e9tats dans Fleet.<\/p>\n<p>\u00a0<\/p>\n<p>Fleet comporte beaucoup d&#8217;\u00e9l\u00e9ments qui interagissent \u00e9troitement les uns avec les autres et ex\u00e9cute de nombreuses op\u00e9rations diff\u00e9rentes, notamment\u00a0:<\/p>\n<p>\u00a0<\/p>\n<ul>\n<li>Le rendu des \u00e9l\u00e9ments de l&#8217;interface utilisateur et les interactions avec les utilisateurs.<\/li>\n<li>Les interactions avec d&#8217;autres services pour obtenir des donn\u00e9es et mettre \u00e0 jour les \u00e9l\u00e9ments de l&#8217;interface utilisateur.<\/li>\n<li>La gestion des fichiers, ce qui inclut leur enregistrement, leur chargement, leur analyse et l&#8217;affichage des diff\u00e9rences entre eux.<\/li>\n<li>L&#8217;orchestration des backends li\u00e9s \u00e0 l&#8217;analyse de code, \u00e0 la saisie semi-automatique et aux r\u00e9sultats des recherches.<\/li>\n<\/ul>\n<p>La plupart de ces op\u00e9rations sont complexes et peuvent affecter la r\u00e9activit\u00e9 de l&#8217;interface. Et Fleet est une application distribu\u00e9e pouvant avoir plusieurs frontends r\u00e9partis sur le r\u00e9seau, ce qui complique encore plus les choses. N\u00e9anmoins, nous devons pr\u00e9senter toutes les informations de fa\u00e7on coh\u00e9rente et correcte \u00e0 nos utilisateurs et leur garantir de pouvoir travailler harmonieusement sur leurs diff\u00e9rents frontends.<\/p>\n<p>\u00a0<\/p>\n<p>En ce qui concerne la gestion d&#8217;\u00e9tats, toutes ces op\u00e9rations peuvent se r\u00e9sumer \u00e0 la lecture ou \u00e0 la mise \u00e0 jour d&#8217;\u00e9tats. Les \u00e9l\u00e9ments de l&#8217;interface utilisateur lisent les \u00e9tats pour fournir aux utilisateurs des donn\u00e9es r\u00e9elles, tandis que les utilisateurs mettent \u00e0 jour les \u00e9tats en modifiant leurs documents et en d\u00e9pla\u00e7ant des \u00e9l\u00e9ments. Chaque minute, des milliers d&#8217;op\u00e9rations de ce type sont ex\u00e9cut\u00e9es, c&#8217;est pourquoi la gestion d&#8217;\u00e9tats est un \u00e9l\u00e9ment cl\u00e9 de Fleet.<\/p>\n<p>\u00a0<\/p>\n<h2><span id=\"Our_principles\" class=\"ez-toc-section\"><\/span>Nos principes<\/h2>\n<p>\u00a0<\/p>\n<p>JetBrains d\u00e9veloppe des IDE depuis plus de 20\u00a0ans. C&#8217;est sur la base de cette exp\u00e9rience que nous avons adopt\u00e9 les principes suivants concernant la gestion d&#8217;\u00e9tats dans Fleet\u00a0:<\/p>\n<p>\u00a0<\/p>\n<h3><span id=\"Principle_1_Dont_block_anyone\" class=\"ez-toc-section\"><\/span>Principe 1 : Ne bloquer personne<\/h3>\n<p>\u00a0<\/p>\n<p>\u00c9crie du code concurrent n&#8217;est pas facile. Pour Kotlin (et dans Fleet), nous utilisons des primitives de concurrence l\u00e9g\u00e8res, appel\u00e9es coroutines, pour organiser notre code concurrent. Si la lecture d&#8217;\u00e9tats d&#8217;un grand nombre de coroutines simultan\u00e9ment ne cause quasiment aucun probl\u00e8me, leur mutation peut \u00eatre dangereuse. L&#8217;approche traditionnelle consiste \u00e0 imposer un verrouillage pour un seul thread r\u00e9dacteur, ce qui g\u00e9n\u00e8re de longues files d&#8217;attente pour lire quelque chose. Nous pensons que cela n&#8217;est pas appropri\u00e9 : les lecteurs devraient avoir la possibilit\u00e9 de lire un \u00e9tat potentiellement l\u00e9g\u00e8rement obsol\u00e8te sans d\u00e9lai. Pour obtenir ce comportement, nous utilisons une variation du <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Multiversion_Concurrency_Control\" target=\"_blank\" rel=\"noopener\">mod\u00e8le MVCC (multiversion concurrency control)<\/a> pour acc\u00e9der aux \u00e9l\u00e9ments d&#8217;\u00e9tat des coroutines. Ces coroutines lisent une version de l&#8217;\u00e9tat ou modifient l&#8217;\u00e9tat en en fournissant la nouvelle version. Nous lisons et modifions l&#8217;\u00e9tat des transactions, ce qui est beaucoup plus facile \u00e0 mettre en \u0153uvre avec le mod\u00e8le MVCC.<\/p>\n<p>\u00a0<\/p>\n<h3><span id=\"Principle_2_Be_efficiently_reactive\" class=\"ez-toc-section\"><\/span>Principe 2 : \u00catre r\u00e9actif et efficace<\/h3>\n<p>\u00a0<\/p>\n<p>Les \u00e9tats changent tout le temps et l&#8217;interface utilisateur doit refl\u00e9ter ces changements aussi rapidement que possible. Si vous avez d\u00e9j\u00e0 programm\u00e9 des animations simples avec votre premier langage de programmation, vous savez comment faire\u00a0: tout effacer et tout redessiner \u00e0 partir de z\u00e9ro. Malheureusement, tout redessiner prend beaucoup de temps. Il est plus judicieux de redessiner uniquement la partie qui a chang\u00e9. Mais pour cela, il faut \u00eatre en mesure de d\u00e9terminer ce qui a chang\u00e9 exactement. Moins il y a de changements, mieux c&#8217;est. Une fois la partie de l&#8217;\u00e9tat qui a chang\u00e9 identifi\u00e9e, il faut d\u00e9terminer aussi rapidement que possible ce qui d\u00e9pend de cette partie et ex\u00e9cuter la coroutine correspondante. Il ne s&#8217;agit pas seulement de r\u00e9agir vite aux changements d&#8217;\u00e9tat, mais de le faire de mani\u00e8re efficace.<\/p>\n<p>\u00a0<\/p>\n<h3><span id=\"Principle_3_Represent_data_wisely\" class=\"ez-toc-section\"><\/span>Principe 3 : Organiser les donn\u00e9es intelligemment<\/h3>\n<p>\u00a0<\/p>\n<p>Les deux premiers principes que nous venons de voir ne seraient que de belles paroles sans le troisi\u00e8me. Il est essentiel de r\u00e9fl\u00e9chir s\u00e9rieusement \u00e0 la fa\u00e7on de stocker et de traiter les donn\u00e9es. Le stockage avec des options de recherches et de modifications avanc\u00e9es n&#8217;est plus r\u00e9serv\u00e9 aux impl\u00e9menteurs de syst\u00e8mes de gestion de bases de donn\u00e9es. Un outil comme Fleet, en tant qu&#8217;IDE distribu\u00e9, en a besoin aussi. Ainsi, nous avons d\u00fb d\u00e9velopper notre propre solution de base de donn\u00e9es interne pour atteindre le niveau de flexibilit\u00e9 et les performances que nous souhaitions.<\/p>\n<p>\u00a0<\/p>\n<h2><span id=\"What_is_a_state\" class=\"ez-toc-section\"><\/span>Qu&#8217;est-ce qu&#8217;un \u00e9tat\u00a0?<\/h2>\n<p>\u00a0<\/p>\n<p>Il y a trois points \u00e0 prendre en compte concernant les \u00e9tats dans Fleet.<\/p>\n<p>\u00a0<\/p>\n<p>Tout d&#8217;abord, les \u00e9tats sont repr\u00e9sent\u00e9s en tant que <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Structure_de_donn%C3%A9es_persistante\" target=\"_blank\" rel=\"noopener\">structure de donn\u00e9es persistante<\/a> avec diff\u00e9rentes versions qui mod\u00e9lisent le changement dans le temps. Pour d\u00e9crire cela, on peut se r\u00e9f\u00e9rer \u00e0 une s\u00e9quence lin\u00e9aire d&#8217;\u00e9poques qui se succ\u00e8dent, ce que l&#8217;on appelle <a href=\"https:\/\/donnywinston.com\/posts\/the-materials-paradigm-and-epochal-time\/\" target=\"_blank\" rel=\"noopener\">mod\u00e8le temporel \u00e0 \u00e9poques<\/a>. Toutes les parties concern\u00e9es (les coroutines\u00a0!) lisent toujours l&#8217;une des \u00e9poques, mais pas forc\u00e9ment la plus r\u00e9cente.<\/p>\n<p>\u00a0<\/p>\n<p>Deuxi\u00e8mement, notre \u00e9tat est une base de donn\u00e9es d&#8217;entit\u00e9s contenant des informations sur tout ce que vous voyez \u00e0 l&#8217;\u00e9cran et tout ce qui se passe en coulisses. Comme c&#8217;est le cas pour de nombreuses bases de donn\u00e9es, ces entit\u00e9s sont li\u00e9es entre elles d&#8217;une fa\u00e7on ou d&#8217;une autre.\u00a0<\/p>\n<p>\u00a0<\/p>\n<p>Troisi\u00e8mement, l&#8217;\u00e9tat et ses mutations peuvent se r\u00e9sumer \u00e0 des triplets de base, appel\u00e9s datoms, qui sont des \u00e9l\u00e9ments de donn\u00e9es primitifs nous permettant d&#8217;atteindre le niveau d&#8217;efficacit\u00e9 dont nous avons besoin. Examinons ces trois id\u00e9es plus en d\u00e9tail.<\/p>\n<p>\u00a0<\/p>\n<h3><span id=\"An_epochal_time_model\" class=\"ez-toc-section\"><\/span>Un mod\u00e8le temporel \u00e0 \u00e9poques<\/h3>\n<p>\u00a0<\/p>\n<p>Pendant longtemps, nos programmes modifiaient leurs \u00e9tats. Malheureusement, mettre \u00e0 jour une seule variable n&#8217;est jamais vraiment suffisant. G\u00e9n\u00e9ralement, nous devons en modifier plusieurs les unes \u00e0 la suite des autres de fa\u00e7on coh\u00e9rente. Que se passe-t-il si quelqu&#8217;un observe notre \u00e9tat dans une forme incompl\u00e8te ou tente de le modifier\u00a0? Imaginons que nous ayons augment\u00e9 la longueur de la cha\u00eene, mais sans avoir fourni de nouveau contenu. Il ne faudrait vraiment pas que nos utilisateurs puissent voir cela. L&#8217;id\u00e9e est de cacher les \u00e9tats incoh\u00e9rents derri\u00e8re une fa\u00e7ade. Passer d&#8217;un \u00e9tat coh\u00e9rent \u00e0 l&#8217;autre prend du temps. C&#8217;est comme si une \u00e9poque succ\u00e9dait \u00e0 une autre.<\/p>\n<p>\u00a0<\/p>\n<p>Le mod\u00e8le temporel \u00e0 \u00e9poques a d&#8217;abord \u00e9t\u00e9 pr\u00e9sent\u00e9 \u00e0 la communaut\u00e9 des programmeurs par Rich Hickey lors de son intervention <a href=\"https:\/\/www.infoq.com\/presentations\/Are-We-There-Yet-Rich-Hickey\/\" target=\"_blank\" rel=\"noopener\">Are We There Yet<\/a> (voir la <a href=\"https:\/\/github.com\/matthiasn\/talk-transcripts\/blob\/9f33e07ac392106bccc6206d5d69efe3380c306a\/Hickey_Rich\/AreWeThereYet.md\" target=\"_blank\" rel=\"noopener\">transcription<\/a>), dans laquelle il exposait ses id\u00e9es concernant l&#8217;impl\u00e9mentation du langage de programmation Clojure. Il explique que nos programmes peuvent vivre dans un environnement immuable et coh\u00e9rent pendant un certain temps. L&#8217;immuabilit\u00e9 facilite l&#8217;impl\u00e9mentation de nombreux \u00e9l\u00e9ments, mais il est impossible de rester dans le m\u00eame environnement pour toujours. Du fait des activit\u00e9s des r\u00e9dacteurs d&#8217;\u00e9tats, un nouvel environnement immuable et coh\u00e9rent succ\u00e8de toujours au pr\u00e9c\u00e9dent.<\/p>\n<p>\u00a0<\/p>\n<p>L&#8217;\u00e9tat de Fleet est accessible sous forme d&#8217;un instantan\u00e9 immuable, une collection de tous les \u00e9l\u00e9ments de l&#8217;\u00e9tat entre lesquels la coh\u00e9rence est garantie. Dans ce mod\u00e8le, la mise \u00e0 jour de l&#8217;\u00e9tat cr\u00e9e un nouvel instantan\u00e9. Afin de garantir la coh\u00e9rence \u00e0 mesure que les \u00e9tats changent, nous impl\u00e9mentons des transactions.<\/p>\n<p>\u00a0<\/p>\n<p>Dans Fleet, le <em>Kernel<\/em> (ou noyau) est responsable de la transition des instantan\u00e9s suite aux activit\u00e9s des r\u00e9dacteurs d&#8217;\u00e9tats et fournit une r\u00e9f\u00e9rence \u00e0 l&#8217;instantan\u00e9 le plus r\u00e9cent. Les parties int\u00e9ress\u00e9es (lecteurs ou les r\u00e9dacteurs) peuvent obtenir cette r\u00e9f\u00e9rence lorsqu&#8217;ils en en besoins, mais ils ne peuvent pas avoir la certitude qu&#8217;elle correspondra bien \u00e0 la version la plus r\u00e9cente de l&#8217;environnement lorsqu&#8217;ils l&#8217;utiliseront. Le noyau est \u00e9galement charg\u00e9 de diffuser les modifications aupr\u00e8s des parties qui en d\u00e9pendent. L&#8217;avantage est qu&#8217;il n&#8217;est pas n\u00e9cessaire de s&#8217;abonner manuellement, il suffit de lire une valeur quelconque pour \u00eatre pr\u00e9venu de ses modifications ult\u00e9rieures.<\/p>\n<p>\u00a0<\/p>\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" class=\"wp-image-255670\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2022\/06\/Below-deck-part-3-Epochal-time.png\" alt=\"\" width=\"800\" \/><\/figure>\n<p>\u00a0<\/p>\n<p>Les r\u00e9dacteurs font la queue pour cr\u00e9er de nouveaux instantan\u00e9s, mais les lecteurs ne sont jamais bloqu\u00e9s. Ils peuvent toutefois recevoir des informations quelque peu obsol\u00e8tes.<\/p>\n<p>\u00a0<\/p>\n<h3><span id=\"The_data_model_for_our_state\" class=\"ez-toc-section\"><\/span>Le mod\u00e8le de donn\u00e9es de notre \u00e9tat<\/h3>\n<p>\u00a0<\/p>\n<p>Nous sommes maintenant pr\u00eats \u00e0 r\u00e9pondre \u00e0 la question : qu&#8217;y a-t-il dans notre \u00e9tat\u00a0? Eh bien, presque tout\u00a0: le contenu du document avec des informations sur le fichier correspondant, toutes les informations d\u00e9duites \u00e0 partir de ce contenu, les positions des curseurs, les plugins charg\u00e9s et leur configuration, les emplacements des vues et des panneaux, etc. Le mod\u00e8le de donn\u00e9es correspondant est d\u00e9crit dans Fleet via des interfaces Kotlin, comme suit\u00a0:<\/p>\n<p>\u00a0<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"kotlin\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">interface DocumentFileEntity : SharedEntity {\n @Unique\n @CascadeDeleteBy\n var document: DocumentEntity\n\n @Unique\n var fileAddress: FileAddress\n\n var readCharset: DetectedCharset\n \/\/ ...\n}\n\ninterface DocumentEntity : SharedEntity {\n var text: Text\n var writable: Boolean\n \/\/ ...\n}\n<\/pre>\n<p>\u00a0<\/p>\n<p>Remarque\u00a0: le type Text est en fait une <a href=\"https:\/\/blog.jetbrains.com\/fr\/fleet\/2022\/02\/dans-les-coulisses-de-fleet-presentation-detaillee-de-l-editeur\/\">corde<\/a>, la structure de donn\u00e9es dont nous avons parl\u00e9 dans le pr\u00e9c\u00e9dent article de cette s\u00e9rie.<\/p>\n<p>\u00a0<\/p>\n<p>Nous utilisons des annotations de propri\u00e9t\u00e9s pour d\u00e9crire les composants des entit\u00e9s et les relations entre eux. Dans cet exemple, une entit\u00e9 fichier de document d\u00e9crit la relation entre un fichier unique sur un p\u00e9riph\u00e9rique de stockage de donn\u00e9es et un document unique que nous avons lu \u00e0 partir de ce dernier. L&#8217;entit\u00e9 document correspondante doit \u00eatre supprim\u00e9e lors de la suppression de l&#8217;entit\u00e9 fichier de document.<\/p>\n<p>\u00a0<\/p>\n<p>Pour maintenir une telle base de donn\u00e9es d&#8217;entit\u00e9s, nous avons impl\u00e9ment\u00e9 notre propre moteur de base de donn\u00e9es : <em>RhizomeDB<\/em>. Il n&#8217;impose pas de hi\u00e9rarchie aux entit\u00e9s, d&#8217;o\u00f9 le nom <a href=\"https:\/\/fr.wikipedia.org\/wiki\/Rhizome\" target=\"_blank\" rel=\"noopener\">Rhizome<\/a>, en r\u00e9f\u00e9rence \u00e0 la tige souterraine qui d\u00e9veloppe des racines et des bourgeons \u00e0 partir de ses n\u0153uds.<\/p>\n<p>\u00a0<\/p>\n<p>Pour acc\u00e9der \u00e0 des entit\u00e9s en tant qu&#8217;objets qui impl\u00e9mentent des propri\u00e9t\u00e9s depuis les interfaces, comme dans les exemples ci-dessus, RhizomeDB fournit une API. Nous pouvons par exemple obtenir un document bas\u00e9 sur une adresse de fichier, comme suit\u00a0:<\/p>\n<p>\u00a0<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"kotlin\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">val document = lookupOne(DocumentFileEntity::fileAddress,\n                         fileAddress)?.document<\/pre>\n<p>\u00a0<\/p>\n<p>L&#8217;objet document impl\u00e9mente maintenant l&#8217;interface DocumentEntity et nous pouvons l&#8217;utiliser pour acc\u00e9der au contenu du document charg\u00e9 dans Fleet.<\/p>\n<p>\u00a0<\/p>\n<p>Notre mod\u00e8le de donn\u00e9es d&#8217;entit\u00e9s est suffisamment flexible pour repr\u00e9senter non seulement les donn\u00e9es, mais aussi le mod\u00e8le de donn\u00e9es lui-m\u00eame. Supposons que nous devions d\u00e9velopper un plugin (nous parlerons des plugins de Fleet dans un prochain article). Les plugins charg\u00e9s font partie de l&#8217;\u00e9tat de Fleet. Tous les plugins partagent certaines donn\u00e9es communes n\u00e9cessaires pour une int\u00e9gration fluide avec l&#8217;application. Cependant, chaque plugin a son propre \u00e9tat, d\u00e9crit avec son propre mod\u00e8le de donn\u00e9es. Ce n&#8217;est pas un probl\u00e8me pour RhizomeDB. Nous pouvons repr\u00e9senter le mod\u00e8le de donn\u00e9es du plugin avec des entit\u00e9s. Lorsque nous chargeons un plugin, nous chargeons \u00e9galement son mod\u00e8le de donn\u00e9es en tant que nouvelle entit\u00e9. Par cons\u00e9quent, le syst\u00e8me de gestion d&#8217;\u00e9tats de Fleet est pr\u00eat \u00e0 accepter les donn\u00e9es d&#8217;\u00e9tat du plugin.<\/p>\n<p>\u00a0<\/p>\n<h3><span id=\"State_as_a_set_of_triples\" class=\"ez-toc-section\"><\/span>Repr\u00e9sentation d&#8217;\u00e9tat en tant que triplet<\/h3>\n<p>\u00a0<\/p>\n<p>Nous ne stockons pas les objets que nous fournit notre API pour travailler avec des entit\u00e9s en tant que tels. Nous les repr\u00e9sentons sous forme de triplets\u00a0: <code>[entity_id, attribute, value]<\/code>. Nous appelons ces triplets <em>datoms<\/em> (ce terme fait r\u00e9f\u00e9rence \u00e0 la base de donn\u00e9es <a href=\"https:\/\/docs.datomic.com\/cloud\/whatis\/data-model.html\" target=\"_blank\" rel=\"noopener\">Datomic<\/a>, sur laquelle nous nous sommes bas\u00e9s pour mod\u00e9liser nos structures de donn\u00e9es).\u00a0<\/p>\n<p>\u00a0<\/p>\n<p>Supposons que l&#8217;<em>id<\/em> de l&#8217;entit\u00e9 d&#8217;un fichier donn\u00e9 faisant r\u00e9f\u00e9rence \u00e0 un document est 18, et que l&#8217;<em>id<\/em> de l&#8217;entit\u00e9 du document correspondant est 19. Les donn\u00e9es seront stock\u00e9es en tant que triplets :<\/p>\n<p>\u00a0<\/p>\n<ul>\n<li><code>[18 :type DocumentFile]<\/code><\/li>\n<li><code>[18 :document 19]<\/code><\/li>\n<li><code>[18 :fileAddress \"~\/file.kt\"]<\/code><\/li>\n<li><code>[18 :readCharset \"UTF-8\"]<\/code><\/li>\n<\/ul>\n<p>Notez que les propri\u00e9t\u00e9s des interfaces deviennent des attributs de triplets. Il existe \u00e9galement divers attributs, tels que <code>:type<\/code>, qui ont des significations particuli\u00e8res. Les types de valeurs d\u00e9pendent des types de propri\u00e9t\u00e9s. Lorsqu&#8217;on fait r\u00e9f\u00e9rence \u00e0 d&#8217;autres entit\u00e9s, les valeurs de propri\u00e9t\u00e9s sont des <em>ID<\/em>.<\/p>\n<p>\u00a0<\/p>\n<p>La structure de triplet apparemment primitive est assez efficace quand il s&#8217;agit d&#8217;interroger des donn\u00e9es. Notre moteur est capable de renvoyer des r\u00e9ponses tr\u00e8s rapides aux requ\u00eates sous la forme d&#8217;un masque\u00a0: <code>[entity_id?, attribute?, value?]<\/code>, dans lequel n&#8217;importe quel composant peut \u00eatre pr\u00e9sent ou manquant. Le r\u00e9sultat d&#8217;une requ\u00eate est toujours un ensemble de datoms qui satisfont le masque donn\u00e9.<\/p>\n<p>\u00a0<\/p>\n<p>Par exemple, nous pouvons demander tous les noms de fichiers des fichiers de documents actuellement charg\u00e9s\u00a0:<\/p>\n<p>\u00a0<\/p>\n<p class=\"has-text-align-center\"><code>[? :fileAddress ?]<\/code><\/p>\n<p>\u00a0<\/p>\n<p>Ou alors nous pouvons rechercher entity_id, qui correspond \u00e0 un fichier avec le nom donn\u00e9\u00a0:<\/p>\n<p>\u00a0<\/p>\n<p class=\"has-text-align-center\"><code>[? :fileAddress \"~\/file.kt\"]<\/code><\/p>\n<p>\u00a0<\/p>\n<p>Pour la deuxi\u00e8me requ\u00eate, l&#8217;ensemble de r\u00e9sultats ne peut pas comprendre plus d&#8217;une r\u00e9ponse en raison de la contrainte d&#8217;unicit\u00e9.<\/p>\n<p>\u00a0<\/p>\n<p>Pour permettre une ex\u00e9cution suffisamment rapide des requ\u00eates, RhizomeDB g\u00e8re quatre index (chacun impl\u00e9ment\u00e9 en tant que <a href=\"https:\/\/en.wikipedia.org\/wiki\/Hash_trie\" target=\"_blank\" rel=\"noopener\">hash trie<\/a>)\u00a0:<\/p>\n<p>\u00a0<\/p>\n<ul>\n<li>Entit\u00e9 | Attribut | Valeur<\/li>\n<li>Attribut | Entit\u00e9 | Valeur<\/li>\n<li>Valeur | Attribut | Entit\u00e9<\/li>\n<li>Attribut | Valeur | Entit\u00e9<\/li>\n<\/ul>\n<p>La famille de fonctions <code>lookup*<\/code> de l&#8217;API RhizomeDB op\u00e8re sur ces index pour trouver les triplets correspondants et construire les objets d&#8217;entit\u00e9s r\u00e9sultants.<\/p>\n<p>\u00a0<\/p>\n<p>RhizomeDB s&#8217;inspire fortement de Datomic, mais apporte plusieurs id\u00e9es nouvelles, telles que le suivi de lecture et la r\u00e9activit\u00e9 des requ\u00eates, ce qui fonctionne avec notre cas d&#8217;utilisation. Ces fonctionnalit\u00e9s nous permettent de faire face aux changements d&#8217;\u00e9tat, comme nous allons le voir bient\u00f4t.<\/p>\n<p>\u00a0<\/p>\n<h2><span id=\"What_is_the_change\" class=\"ez-toc-section\"><\/span>Comment g\u00e9rer le changement ?<\/h2>\n<p>\u00a0<\/p>\n<p>Un \u00e9tat immuable n&#8217;a rien de bien int\u00e9ressant. Cela devient int\u00e9ressant lorsque nous effectuons des modifications. Nous aimerions savoir ce qui a chang\u00e9 dans l&#8217;\u00e9tat et quels \u00e9l\u00e9ments de l&#8217;interface utilisateur doivent \u00eatre mis \u00e0 jour. Pour g\u00e9rer les modifications, nous avons impl\u00e9ment\u00e9 les trois id\u00e9es suivantes\u00a0:<\/p>\n<p>\u00a0<\/p>\n<ul>\n<li>Nous enregistrons de fa\u00e7on pr\u00e9cise ce qui a chang\u00e9 en tant que <em>nouveaut\u00e9 li\u00e9e au changement<\/em>.<\/li>\n<li>Nous effectuons un suivi des recherches que font les lecteurs.<\/li>\n<li>Nous d\u00e9terminons quelles requ\u00eates donneraient de nouveaux r\u00e9sultats en raison de ce changement.<\/li>\n<\/ul>\n<p>Examinons ces id\u00e9es plus en d\u00e9tail et voyons comment elles fonctionnent dans Fleet.<\/p>\n<p>\u00a0<\/p>\n<h3><span id=\"Novelty_values\" class=\"ez-toc-section\"><\/span>Valeurs de nouveaut\u00e9<\/h3>\n<p>\u00a0<\/p>\n<p>Souvenez-vous que nous cherchons autant que possible le maintien de l&#8217;immuabilit\u00e9, nous ne sommes donc pas autoris\u00e9s \u00e0 modifier les valeurs. Rappelez-vous aussi que notre \u00e9tat prend la forme d&#8217;un instantan\u00e9 contenant un ensemble de triplets avec des identifiants (ID) d&#8217;entit\u00e9s, des attributs et leurs valeurs, repr\u00e9sentant les entit\u00e9s de donn\u00e9es correspondantes. Au lieu de modifier les valeurs des attributs, nous produisons un nouvel instantan\u00e9 d&#8217;\u00e9tat avec la nouvelle valeur de l&#8217;attribut que nous voulons modifier. La modification consiste alors simplement dans la suppression d&#8217;une ancienne valeur et l&#8217;ajout d&#8217;une nouvelle. Par exemple, pour renommer un fichier, nous proc\u00e9dons de la fa\u00e7on suivante\u00a0:<\/p>\n<p>\u00a0<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">- [18 :fileAddress \"~\/file.kt\"]\n+ [18 :fileAddress \"~\/newFile.kt\"]<\/pre>\n<p>\u00a0<\/p>\n<p>Notez que ces deux op\u00e9rations doivent \u00eatre ex\u00e9cut\u00e9es dans une transaction, \u00e0 d\u00e9faut de quoi, l&#8217;\u00e9tat n&#8217;aura pas de nom de fichier. L&#8217;ex\u00e9cution de cette transaction g\u00e9n\u00e8re un nouvel instantan\u00e9 d&#8217;\u00e9tat avec un nouveau nom de fichier.<\/p>\n<p>\u00a0<\/p>\n<p>Finalement, toute modification est juste un ensemble de retraits et d&#8217;ajouts de datoms. De nombreux retraits et ajouts pour diff\u00e9rents entit\u00e9s et attributs peuvent r\u00e9sulter d&#8217;une transaction. De plus, la diff\u00e9rence entre deux instantan\u00e9s est aussi un ensemble de retraits et d&#8217;ajouts de datoms. \u00c0 partir des identifiants des entit\u00e9s et des attributs de l&#8217;ensemble de modifications nous pouvoir savoir pr\u00e9cis\u00e9ment quels composants d&#8217;\u00e9tat ont \u00e9t\u00e9 modifi\u00e9s lors d&#8217;une transaction. Cela ce que l&#8217;on appelle la <em>nouveaut\u00e9<\/em> li\u00e9e au changement. Une fois la transaction ex\u00e9cut\u00e9e, nous enregistrons ces valeurs de <em>nouveaut\u00e9<\/em>.<\/p>\n<p>\u00a0<\/p>\n<h3><span id=\"Read-tracking_and_query_reactivity\" class=\"ez-toc-section\"><\/span>Suivi de lecture et r\u00e9activit\u00e9 des requ\u00eates<\/h3>\n<p>\u00a0<\/p>\n<p>Nous savons que les lecteurs acc\u00e8dent aux donn\u00e9es de l&#8217;\u00e9tat via des requ\u00eates. Les requ\u00eates ont la forme d&#8217;un masque. Il est facile de suivre l&#8217;ensemble des masques depuis une fonction donn\u00e9e. Lorsque nous avons ces informations pour toutes nos fonctions, nous pouvons d\u00e9terminer quelles fonctions d\u00e9pendent de quel masque.<\/p>\n<p>\u00a0<\/p>\n<p>Une fois toutes les modifications r\u00e9alis\u00e9es, nous obtenons ses valeurs de nouveaut\u00e9. En examinant tous les masques interrog\u00e9s, on peut voir quelles requ\u00eates sont affect\u00e9es par le changement. Gr\u00e2ce au suivi de lecture, nous savons d\u00e9sormais quelles fonctions sont affect\u00e9es. Par cons\u00e9quent, nous pouvons invalider les \u00e9l\u00e9ments de l&#8217;interface utilisateur qui appellent ces fonctions. Cela augmente la r\u00e9activit\u00e9 de l&#8217;interface utilisateur.<\/p>\n<p>\u00a0<\/p>\n<p>Le suivi de lecture ne nous sert pas uniquement \u00e0 mettre \u00e0 jour les \u00e9l\u00e9ments de l&#8217;interface utilisateur. Il s&#8217;agit d&#8217;un m\u00e9canisme g\u00e9n\u00e9ral qui permet d&#8217;exploiter des sch\u00e9mas utiles pour la programmation r\u00e9active. Par exemple, si nous avons une fonction qui interroge l&#8217;\u00e9tat, nous pouvons facilement la transformer en <a href=\"https:\/\/kotlinlang.org\/docs\/flow.html\" target=\"_blank\" rel=\"noopener\">flux asynchrone<\/a>. Chaque fois que des changements d&#8217;\u00e9tat influent sur le r\u00e9sultat d&#8217;une telle fonction, nous \u00e9mettons un nouvel \u00e9l\u00e9ment de flux. Nous pouvons \u00e9galement mettre les r\u00e9sultats d&#8217;une requ\u00eate en cache en toute s\u00e9curit\u00e9, sans risquer d&#8217;avoir des valeurs obsol\u00e8tes dans le cache. Lorsque la valeur est mise \u00e0 jour dans l&#8217;\u00e9tat, nous le savons imm\u00e9diatement.<\/p>\n<p>\u00a0<\/p>\n<h2><span id=\"Summary\" class=\"ez-toc-section\"><\/span>En r\u00e9sum\u00e9<\/h2>\n<p>\u00a0<\/p>\n<p>Dans cet article, nous avons utilis\u00e9 un mod\u00e8le temporel \u00e0 \u00e9poques via une s\u00e9rie d&#8217;instantan\u00e9s immuables, et construit une repr\u00e9sentation de donn\u00e9es intelligente afin de maintenir notre \u00e9tat. Nos donn\u00e9es existent \u00e0 deux niveaux\u00a0: en tant qu&#8217;entit\u00e9s de donn\u00e9es pratiques pour le travail des d\u00e9veloppeurs et en tant que triplets pour une recherche efficace. Lorsque nous effectuons une modification, nous enregistrons ce qui a \u00e9t\u00e9 modifi\u00e9, d\u00e9terminons qui est int\u00e9ress\u00e9 par ces changements et les utilisons pour mettre \u00e0 jour les \u00e9l\u00e9ments de l&#8217;interface utilisateur correspondants.<\/p>\n<p>\u00a0<\/p>\n<p>Maintenant que vous avez toutes ces informations, nous allons pouvoir parler de la nature distribu\u00e9e de l&#8217;\u00e9tat de Fleet et des m\u00e9canismes transactionnels qui nous permettent de le faire \u00e9voluer de fa\u00e7on coh\u00e9rente. Nous verrons cela dans le prochain article de cette s\u00e9rie. Restez \u00e0 l&#8217;\u00e9coute\u00a0!<\/p>\n<p><em>Auteur de l&#8217;article original en anglais<\/em> :<\/p>\n<p>\u00a0<\/p>\n\n    <div class=\"about-author \">\n        <div class=\"about-author__box\">\n            <div class=\"row\">\n                <div class=\"about-author__box-img\">\n                    <img decoding=\"async\" src=\"https:\/\/secure.gravatar.com\/avatar\/?s=200&#038;r=g\" width=\"200\" height=\"200\" alt=\"\" loading=\"lazy\"  class=\"avatar avatar-200 wp-user-avatar wp-user-avatar-200 photo avatar-default\">\n                <\/div>\n                <div class=\"about-author__box-text\">\n                                                        <\/div>\n            <\/div>\n        <\/div>\n    <\/div>\n","protected":false},"author":813,"featured_media":260836,"comment_status":"closed","ping_status":"closed","template":"","categories":[623,89],"tags":[91],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/fleet\/260820"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/fleet"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/types\/fleet"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/users\/813"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/comments?post=260820"}],"version-history":[{"count":9,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/fleet\/260820\/revisions"}],"predecessor-version":[{"id":262308,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/fleet\/260820\/revisions\/262308"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/media\/260836"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/media?parent=260820"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/categories?post=260820"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/tags?post=260820"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/fr\/wp-json\/wp\/v2\/cross-post-tag?post=260820"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}