Aperçu
Un petit helper de consultation pour les développeurs RDBMS qui découvraient MongoDB pour la première fois a évolué pour répondre aux exigences des projets réels, expliquant l'histoire et les critères de conception jusqu'à devenir une couche de commodité MongoDB orientée pilote dans 1.0.0.
Histoire d'origine
J'ai développé principalement autour des RDBMS pendant environ 4 ans et j'étais surtout familier avec AWS RDS pour les environnements de production. En commençant un nouveau projet personnel, j'ai choisi MongoDB pour la première fois et j'ai réalisé qu'il s'agissait d'un outil capable de transformer considérablement ma productivité de développement, plutôt que d'être simplement une base de données à schéma flexible.
MongoDB Atlas était particulièrement un nouveau monde pour moi. J'ai pu utiliser la recherche spécialisée et la recherche vectorielle dans une seule plateforme de données, ce qui était beaucoup plus pratique selon mes critères, ayant toujours utilisé AWS RDS pour des tâches opérationnelles comme les sauvegardes, la surveillance et l'évolutivité. Si des fonctionnalités et une échelle plus professionnelles étaient nécessaires, je pourrais envisager de séparer les moteurs de recherche ou les bases de données vectorielles en tant que services distincts, mais pour la phase de création et d'exploitation rapide d'un projet, la portée que pouvait offrir Atlas était très attrayante.
Le problème était que, étant novice avec MongoDB, écrire des requêtes dynamiques était très lent. Au début, la forme findBy... du repository suffisait, mais à mesure que les conditions de recherche devenaient plus complexes, je devais assembler directement Query, Criteria et les expressions d'opérateurs. Dans un état d'inconnu, l'assemblage répétitif des conditions était sujet aux erreurs de frappe, et écrire continuellement le même type de code ralentissait considérablement le développement.
Commencé avec un DSL simple pour la consultation
Ainsi, la première chose que j'ai créée était une petite classe pour simplifier la création de l'opération R dans CRUD. C'est le point de départ de ce qui est maintenant connu sous le nom de ReactiveMongoDsl. À l'époque, il n'y avait aucune fonctionnalité comme l'agrégation, le pipeline ou le lookup, et je me contentais d'assembler un peu plus rapidement Query, Criteria et la pagination de Spring Data ReactiveMongoTemplate. Je pensais que le reste des opérations pouvait être suffisant en utilisant directement le ReactiveMongoTemplate de base ou le pilote MongoDB.
Cependant, l'avis d'un développeur avec qui je travaillais était différent. Étant également novice avec MongoDB, il me demandait toujours si la fonctionnalité dont il avait besoin, comme upsert ou bulk, était présente dans le DSL que j'avais créé. Je pouvais lui dire d'utiliser les API de base, mais je pensais qu'il rencontrerait les mêmes difficultés que j'avais ressenties en apprenant MongoDB pour la première fois. Ainsi, j'ai commencé à ajouter une fonctionnalité à la fois à ReactiveMongoDsl chaque fois qu'elle était nécessaire.
Au début, il y avait environ 600 lignes, et même quand cela a atteint 1 000 lignes, je ne pensais pas qu'il était nécessaire de le diviser en plusieurs classes. Quand cela a atteint environ 2 000 lignes, j'ai commencé à réfléchir, mais la structure pour la séparation semblait en fait devenir plus complexe, et surtout, j'avoue que cela devenait ennuyeux. Lorsque cela a dépassé 5 000 lignes, je n'ai plus pu retarder et j'ai déplacé certaines fonctionnalités pouvant être séparées, mais j'étais déjà dans une situation où il était difficile de redéfinir le flux principal de manière détaillée. La raison pour laquelle la classe principale est si volumineuse n'est pas que j'ai conçu un énorme DSL dès le départ, mais plutôt qu'elle est le résultat d'une accumulation d'histoires de fonctionnalités nécessaires dans des projets réels.
Après avoir ajouté des fonctionnalités comme la consultation, l'agrégation, le lookup, les mises à jour atomiques, le bulk, l'historique, Atlas Search et Vector Search, j'ai réalisé que la plupart des fonctionnalités dont j'avais souvent besoin lors de l'utilisation de MongoDB dans des projets Java étaient déjà présentes. Plutôt que de les garder comme un helper interne dans un projet, j'ai décidé de les séparer pour qu'elles puissent être utilisées de la même manière dans plusieurs projets, ce qui a conduit à la naissance de reactive-mongo-dsl.
D'un helper Spring à une bibliothèque orientée pilote.
Le point de départ était un helper pour utiliser ReactiveMongoTemplate plus facilement, mais à mesure que les fonctionnalités ont augmenté et que j'ai commencé à les réutiliser dans plusieurs projets, j'ai jugé que le modèle d'exécution d'un cadre spécifique ne devait pas définir les limites de la bibliothèque. Le cœur de 1.0.0 repose sur MongoExecutionContext comme contrat d'exécution et utilise directement le MongoDB Reactive Streams Driver.
Ce changement n'est pas destiné à exclure Spring Data MongoDB. Dans les applications Spring, il est possible de connecter la nomination des collections de ReactiveMongoTemplate, MongoConverter, la conversion personnalisée et l'audit réactif à l'adaptateur MongoExecutionContext. Le cœur est maintenu indépendamment du cadre, et seules les applications nécessaires continuent d'utiliser la configuration Spring existante.
En même temps, il est devenu plus clair de ne pas réimplémenter dans le DSL les fonctionnalités déjà bien fournies par le pilote MongoDB. L'agrégation générale peut recevoir directement des étapes Bson, et pour Search/Vector, des échappatoires comme SearchOperator, VectorSearchQuery, driverOptions(...), stage(Bson) sont disponibles. C'est un choix pour permettre d'utiliser de nouvelles fonctionnalités fournies par le pilote sans attendre la prochaine version du DSL.
Philosophie de développement
Ne cache pas MongoDB.
Je ne voulais pas créer un ORM qui transforme les fonctionnalités de MongoDB en concepts d'autres bases de données. Je relie les requêtes, l'agrégation, les transactions, Atlas Search et Vector Search de MongoDB dans un flux plus court et plus facile à découvrir.
J'ajoute uniquement les fonctionnalités réellement nécessaires.
Ce n'est pas une bibliothèque conçue après avoir d'abord planifié la liste des fonctionnalités. Nous avons ajouté la recherche, l'upsert et le traitement en masse car ils étaient nécessaires dans le projet opérationnel, et nous avons intégré Atlas Search et Vector Search pour la fonctionnalité de recherche.
Nous privilégions la productivité plutôt que la perfection structurelle.
Nous ne prétendons pas qu'une classe principale volumineuse constitue une structure idéale. Au début, il était plus important que l'équipe puisse traiter rapidement des opérations MongoDB peu familières de la même manière, et nous privilégions toujours le flux d'utilisation réel plutôt que l'abstraction elle-même.
Nous ne recréons pas ce que le Driver fait déjà.
Si le MongoDB Java Driver dispose d'un constructeur typé, nous utilisons ce type autant que possible. Les combinaisons répétitives que le DSL peut réduire de manière significative sont fournies par une API de commodité, mais nous ne dupliquons pas l'API du Driver sous un autre nom.
Il doit être possible de descendre vers l'API de base.
Nous ne pensons pas que le DSL remplace toutes les situations. Nous laissons des échappatoires comme Bson, les filtres/tri/opérateurs/options du Driver, et le personnaliseur de publisher, permettant d'utiliser directement le MongoDB Driver si nécessaire.
Les quatre flux abordés dans le document actuel.
Requête Mongo générale.
C'est le flux à partir duquel cette bibliothèque a été initialement lancée. Les conditions et l'exécution sont configurées dans l'ordre execute* → fields(...) → end() → find/findAll/count/delete/exists/atomicUpdate.
Agrégation native du Driver.
Pour contrôler directement depuis le premier stage du pipeline ou utiliser les nouvelles fonctionnalités d'agrégation fournies par le Driver, nous utilisons le flux aggregation().stage(Bson). Le DSL peut transmettre des stages déjà fournis par le Driver, comme $score et $scoreFusion, sans les réimplémenter.
Recherche Atlas
C'est un flux ajouté en appliquant Atlas Search au projet sans d'abord introduire un service de recherche distinct. Nous configurons text, compound, score, highlight et sequence token après search(index).
Recherche vectorielle
C'est un flux ajouté pour relier la recherche d'embedding à l'intérieur de MongoDB. Nous configurons le vecteur de requête ou l'embedding automatisé, ANN/ENN, le pré/post filtre et les options d'embedding nested/array.
Modèle d'exécution de la version 1.0.0.
La séparation entre les requêtes générales et Search/Vector est faite pour ne pas masquer les contraintes réelles du pipeline de MongoDB. $search et $vectorSearch ont des contraintes de premier stage, et les fonctionnalités qui nécessitent que l'appelant contrôle l'ensemble du premier stage peuvent descendre vers aggregation().
Portée utilisée dans des projets réels
• Nous passons des paires sans condition comme null pour assembler des conditions de recherche dynamiques par écran.
• Nous séparons les données et le totalCount avec PageStream pour maintenir le flux de traitement en masse dans un état de publisher réactif.
• Nous créons des opérations sûres contre les exécutions en double avec atomicUpdate().upsertOne().document().setOnInsert(...).
• Nous stockons les données collectées et les données d'intégration externe par bulk upsert sur la base de l'ID ou de la clé métier.
• Nous retournons les résultats de lookup et le nombre total dans un seul pipeline avec executeLookupAndCount.
• Nous utilisons le token de séquence d'Atlas Search et la requête d'embedding automatisée de Vector Search.
• Les nouveaux stages d'agrégation fournis par le Driver se connectent directement via aggregation().stage(Bson) ou stage(Bson) de Search/Vector.
• Si l'atomicité est nécessaire entre les opérations DSL, nous spécifions la portée de la transaction ClientSession avec getTxJob(...).