mongoxx, driver C++ alternatif pour MongoDB
J'utilise MongoDB pour une poignée de projets mais je n'ai jamais eu besoin des différents drivers python, php ou encore javascript. Aucun, sauf le driver C++. Bon, OK, si on considère que le shell mongo est un driver javascript, alors oui, je l'utilise beaucoup[1].
Le driver C++ est intimement lié au projet MongoDB dans la mesure où il est maintenu par les core developers -une partie du staff de 10gen- et qu'il partage la plupart de ses sources avec celles du server.
Il y a des avantages à ce qu'un tel driver soit à ce point corrélé au server. Premièrement, il fait partie du cercle fermé des drivers officiels, ceux développés par 10gen. Deuxièmement, des améliorations au niveau du server peuvent être répercutées sur le driver puisque les sources sont communes (par exemple les mécanismes de réplication). Enfin, comme la plupart des tests unitaires et fonctionnels du projet s'appuient sur ce driver, nul besoins de préciser que ses bugs sont corrigés en priorité.
Maintenant, les désavantages, car il y en a.
Compiler le driver, c'est à dire obtenir libmongoclient et une série de fichiers headers n'est pas si simple que ça. Le build system de MongoDB est basé sur SCons, un software construction tool écrit en python. SCons est un très bon outil. Un projet est construit (compilé, installé etc) à l'aide d'instructions python présentes dans un fichier SConstruct ainsi que, optionnellement, autant de fichiers SConscript qu'il y a de sous-répertoires et sous-projets.
MongoDB est une collection de projets. Le server principal mongod, mais aussi des tools tels que mongosniff, mongodump, le shell mongo, des exécutables pour les tests ou le driver C++ qui est, comme nous l'avons vu, une librairie qui se compile et est pleinement intégrée au source tree principal.
MongoDB n'a recours qu'à un seul et unique fichier SConstruct pour compiler et installer tous les sous-projets pour toutes les plateformes (diverses distribs Linux, Solaris, Windows, 32bit, 64bits etc). Le fichier a grossi exponentiellement. Celui-ci est régulièrement tweaké au gré des besoins imposés par un rythme de développement soutenu.
Cela n'est pas gênant dans la mesure où la méthode conseillée pour installer MongoDB est de télécharger des packages prêts à l'emploi pour une plateforme donnée. Nul besoin de lancer ni même installer SCons. Comme la plupart des applications utilisant MongoDB sont écrites en ruby, python ou php[2] et que leurs drivers respectifs sont développés sur des canaux séparés, la majorité des utilisateurs n'a que faire des subtilités du build system.
Seul les core devs, les buildbots et les utilisateurs du driver C++ (et bien sûr les hackers en tout genre;) sont amenés à s'adapter. C'est mon cas. Depuis le début, je maintiens un fork qui me permet de m'accomoder en douceur, si je puis dire.
Pendant un temps, j'ai soumis quelques patches en fonctions des problèmes que je rencontrais. Par exemple le fait d'utiliser spidermonkey en provenance du dépot mercurial de mozilla-central plutôt que des tarballs individuels ou des packages (j'ai depuis switché pour V8). Ou encore le fait d'utiliser diverses versions de GCC avec différents flags.
Le driver C++ a toujours eu un statut un peu bâtard, principalement parce que personne ne l'utilise à part le projet MongoDB, ou presque. Récemment, les choses se sont dégradées et à moins de bidouiller le build system, il n'est plus possible d'effectuer une installation conforme dans un répertoire cible contenant les includes et lib nécessaires, et donc ne pas pénaliser les projets qui dépendent d'une telle installation.
Grâce à mon fork, ça va, je gère.. pour l'instant. Ce n'est pas le cas de tout le monde. Les gars de 10gen ont prévu de normaliser la situation. Comme ils savent un peu (légèrement;) mieux que moi ce qu'il faut faire pour opérer un découplage propre sans briser tout le reste, je prends mon mal en patience.
...jusqu'à il y a quelques jours. Après tout, quel intérêt ai-je à dépendre de ce driver, qui même correctement compilé et installé, n'est pas dénué de défauts comme nous allons le voir.
Quand j'ai commencé à écrire mes premiers codes utilisant ce driver, ce qui m'a le plus étonné et frustré était à quel point les développeurs ont été peu précautionneux au regard des pratiques élémentaires d'importation des namespaces C++.
En gros, le moindre include de header mongo avait pour conséquence d'importer tous les symbols de la librairie standard, mais aussi quelques namespaces de la librairie Boost, dans le namespace global. C'est simple, Il était impossible d'utiliser le driver C++ pour un projet autre que MongoDB lui-même. Heureusement, un fix d'antologie -par votre serviteur- a permis de limiter les dégâts à moindre frais ;) Maintenant, c'est le namespace mongo qui est pollué. Un soin curatif complet demanderait une qualification complète de tous les noms dans tous les fichiers headers et des importations sur demande dans les fichiers sources. Une tâche longue et fastidieuse que personne n'a eu le courage d'entreprendre.
mongoxx
Du coup j'ai entrepris d'écrire mon propre driver C++, mongoxx, entièrement découplé des sources de MongoDB. L'idée, c'est que ce soit découplé, d'accord, mais surtout explorer certaines librairies Boost que je n'ai jamais encore utilisé. Je pense à Proto ou Move dont la review est pending. Pour la partie communication, Asio bien entendu.
Quid de BSON, le format "JSON binaire" omniprésent dans MongoDB? Je n'ai pas trouvé de container suffisamment souple avec lequel travailler pour implémenter les specs BSON. Un std::vector<char> ne fait pas complètement l'affaire, pas plus qu'un boost::array.
Du coup je me suis monté un container binaire générique qui repose ultimement sur des malloc/realloc et que j'utilise pour implémenter BSON mais aussi pour construire les messages envoyés au server qui contiennent souvent des docs BSON en plus de headers/flags spécifiques.
Mes premiers essais avec Proto sont vraiment fantastiques ;) Cette librairie est une tuerie. Un petit teaser:
// 'doc_', 'genoid', 'undefined', 'now' sont des terminals proto.
// l'espace mémoire requis est calculé à la compilation
// si possible (pod types)
doc d = doc_
("_id", genoid)
("machin", "ok")
("truc", undefined)
("embed", doc_
("val", 3.14)
("date", now))
;
d << "last" << 42; // grows as needed
// STL iterator ready
std::for_each(d.begin(), d.end(), [](elem const& e){
pretty_print(e);
});
Le but c'est que l'interface du driver soit simple et à peu près conforme aux conventions de la librairie standard ainsi que Boost, ce que le driver C++ de MongoDB n'est pas du tout. Par exemple être compatible avec les itérateurs pour parcourir des documents BSON ou des results sets et donc pouvoir y appliquer les algorithms standards.
Actuellement j'ai un chantier de proofs of concept que je veux résorber et structurer, en commençant par y ajouter des tests unitaires avant d'aller plus loin. Je push ensuite tout ça sur github. Comme je souhaite me débarrasser complètement de libmongoclient, je ne vais pas vous cacher que j'ai encore pas mal de boulot ;)
[1] Une bonne partie de l'administration de ce site se fait à partir d'un shell mongo.
[2] MongoDB survey results publié le 18 Fev 2010.