Crabster et les autres
{% raw %} Ce que font les outils du même genre, ce qu'ils ont résolu que Crabster n'a pas, et ce qu'ils ont tranché autrement. Écrit pour arbitrer — pas pour vendre.
Sur quoi cette page s'appuie. Les affirmations sur JHipster ont été vérifiées sur sa documentation et ses tickets, listés en fin de page. Celles qui ne l'ont pas été sont marquées (non vérifié). JHipster évolue vite : revérifiez avant de vous appuyer sur un détail.
Celles sur Crabster ont été vérifiées contre le dépôt, en l'exécutant, le 16 septembre 2026. C'est daté parce que cette page a déjà eu tort dans le sens le plus bête : elle a annoncé pendant plusieurs jours trois manques que le code avait fermés entre-temps. Une page de comparaison vieillit dans les deux sens.
Et le biais est évident : cette page est écrite depuis Crabster. Ce qui suit essaie de compenser en étant plus dur avec Crabster qu'avec les autres, parce que c'est le seul des deux dont les défauts nous coûtent quelque chose.
Quatre familles, et une seule a le problème de fusion🔗
Le classement qui explique tout le reste : qu'advient-il du code généré ?
| Famille | Exemples | Régénérer, ça donne quoi |
|---|---|---|
| 1. Générer et déposséder | rails g scaffold, Spring Initializr, cargo new | Rien : il n'y a pas de régénération. Le code est à vous à la seconde où il est écrit |
| 2. Générer une part qu'on ne touche jamais | sqlc, Prisma Client, OpenAPI Generator | Indolore, parce que la frontière est physique : le code généré est dans des fichiers que vous n'éditez pas |
| 3. Ne rien générer | Hasura, PostgREST, Supabase | Sans objet : l'API est dérivée du schéma à l'exécution |
| 4. Générer un projet entier destiné à être édité | JHipster, Crabster | Un problème de fusion, et c'est le prix de la promesse |
La famille 4 est la seule qui promette « une application complète et c'est votre code ». La fusion est ce que coûte cette promesse — pas un défaut d'implémentation qu'on pourrait corriger en travaillant mieux.
Les familles 2 et 3 méritent d'être regardées avant de choisir la 4. Si votre besoin est « une API CRUD sur un schéma », Hasura ou PostgREST vous la donnent sans une ligne de code, et le prix — vous ne possédez pas le moteur, la logique arbitraire n'y entre pas — n'est pas toujours le mauvais choix.
JHipster🔗
Dix ans d'existence, un écosystème considérable, du frontend, des microservices, des dizaines d'options de base et de framework. Crabster est un projet jeune d'un seul auteur. Toute comparaison qui oublierait cela serait malhonnête.
L'architecture est pourtant étonnamment proche — Crabster suit le même patron :
| JHipster | Crabster |
|---|---|
JDL (entity, relationship, enum, application) | CDL (record, ref, enum, service) |
.yo-rc.json + .jhipster/*.json | .crabster/project.toml + model.cdl |
Needles — marqueurs jhipster-needle-* où le générateur injecte | Slots — _into/<fichier>/<slot>.tera |
Blueprints (generator-jhipster-* sur npm) | Blueprints (répertoires de modules) |
jhipster upgrade | crabster upgrade — même intention, autre mécanique (voir plus bas) |
Les needles et les slots sont la même idée : un point d'injection nommé dans un fichier qu'un autre générateur possède. Les blueprints aussi, en plus mature chez eux — un blueprint JHipster peut remplacer un sous-générateur entier, pas seulement un template.
Ce que JHipster a résolu, et où en est Crabster🔗
Cette section listait quatre manques. Trois sont fermés depuis, le quatrième à moitié — et ce qui reste vraiment devant n'est aucun des quatre.
| JHipster | Crabster | |
|---|---|---|
| Objets liés dans les réponses | Les DTO MapStruct portent l'objet lié | Fermé. ?expand=customer,product remplace l'identifiant par l'enregistrement, avec une requête par référence pour toute la page et non une par ligne. Un niveau, pas une arborescence : ce qui est expandé porte l'identifiant du suivant |
| Filtres riches | equals, notEquals, specified, in, contains, greaterThan, lessThan… générés en JPA Criteria | Fermé, sauf in. Les mêmes opérateurs, choisis d'après le type de la colonne — contains sur du texte, les quatre comparaisons sur ce qui s'ordonne, specified sur ce qui est facultatif. Et un paramètre mal orthographié reçoit un 400 qui liste ceux qui existent, là où l'ignorer renverrait la table entière — ce qui ressemble à un résultat |
| Recherche | search with elasticsearch : une intégration complète | Fermé. search meilisearch donne un endpoint plein texte par enregistrement, sur les tables comme sur les documents : ce qui est indexé est le document que l'API renvoie, donc le module ne demande jamais où les enregistrements sont rangés |
| Colonnes d'audit | AbstractAuditingEntity — createdBy, createdDate, lastModifiedBy, lastModifiedDate (non vérifié) | À moitié. @audited donne createdAt et updatedAt, écrites par le code au moment de l'écriture. createdBy et lastModifiedBy demandent une identité, donc un lien entre le module d'authentification et celui des enregistrements que rien ne tisse aujourd'hui |
Ce qui reste devant ne se rattrape pas en écrivant du code : dix ans, un écosystème et des gens. Des dizaines de blueprints publiés par d'autres que l'équipe du générateur, une base d'utilisateurs qui remonte les défauts avant qu'ils n'atteignent tout le monde, et une réponse à des besoins que ce dépôt n'a pas encore rencontrés — cache distribué, messagerie, envoi d'e-mails, i18n de l'application engendrée, écran d'administration des comptes (non vérifié). Crabster n'a rien de tout cela, et il n'a surtout encore aucun utilisateur.
Ce que ni l'un ni l'autre n'a résolu🔗
- Retirer ou renommer une entité. Chez JHipster on réédite le JDL et on
réimporte ; supprimer laisse des fichiers orphelins et un changelog à écrire
(non vérifié). Chez Crabster,
crabster apply --forceretire un enregistrement et écrit leDROP TABLE; renommer, ni l'un ni l'autre. - La suppression logique. Ni l'un ni l'autre en cœur.
Ce que les deux ont tranché différemment🔗
| JHipster | Crabster | |
|---|---|---|
| Base de développement ≠ production | devDatabaseType h2Disk / prodDatabaseType postgresql | Refusé. Une base par projet |
| Sécurité du CRUD | Fermé par défaut, on ouvre ensuite | Ouvert par défaut, on ferme en nommant l'extracteur |
| Frontend | Généré (Angular, React, Vue), dans le cœur | Pas d'application UI dans le cœur, par décision. Un client typé — TypeScript et Rust — généré depuis le modèle ; les applications UI en blueprints, et il en existe trois — React, Vue, Angular — écrites contre ce client et livrées dans le binaire. La différence n'est pas « avec ou sans interface » mais qui porte le coût d'un framework UI qui casse (ADR-0003) |
Le premier mérite un mot, parce que c'est le seul où je pense que Crabster a
raison contre l'usage établi : le schéma n'est pas le même d'une base à
l'autre. Un Timestamp est un DATETIME sur MySQL et un timestamptz
ailleurs ; un Decimal ne fait pas l'aller-retour sur SQLite. Tester sur H2 et
déployer sur PostgreSQL, c'est faire passer ses tests contre un schéma que la
production n'a jamais — et les tickets JHipster sur « ça marche en H2, ça casse
après » ne sont pas rares. Le besoin derrière l'option est réel ; Crabster y
répond par des testcontainers contre la base réellement visée.
jhipster upgrade : leur réponse au problème de la famille 4🔗
Elle est astucieuse et brutale. La commande :
- crée ou récupère une branche orpheline
jhipster_upgrade; - y régénère l'application entière (
--force --with-entities) avec la nouvelle version du générateur, et commite ; - fusionne cette branche dans la vôtre par un
git merge.
Les conflits, c'est git qui les pose et vous qui les résolvez. Sur une application peu personnalisée c'est indolore ; sur une application où vous avez beaucoup écrit, c'est une session de résolution réputée pénible.
crabster upgrade : la même intention, une autre mécanique🔗
Crabster fait la même chose, avec une information que JHipster n'a pas : il
sait quels fichiers vous avez touchés, parce qu'il les a empreints en les
écrivant. La commande régénère depuis .crabster/, puis tranche fichier par
fichier — réécrit ce qui est resté tel qu'il l'avait écrit, laisse intact ce que
vous avez édité et dépose sa version dans .crabster/incoming/, supprime ce
qu'il ne produit plus s'il est intact.
Sur un fichier édité, la fusion est une vraie fusion à 3 voies, comme la leur.
La différence est l'ancêtre commun : JHipster le fait produire par git à partir
d'une branche orpheline régénérée ; Crabster le garde lui-même, dans
.crabster/snapshot/, une copie de chaque fichier tel qu'il l'a écrit.
| JHipster | Crabster | |
|---|---|---|
| Ce qui arbitre | git merge | une fusion 3-voies sur la copie que l'outil a gardée |
| Ce qui distingue votre travail | rien : git compare deux textes | l'empreinte enregistrée — l'outil sait qui a écrit chaque fichier |
| Exige git | oui, et une branche orpheline | non |
| En cas de conflit | des marqueurs dans vos fichiers | vos fichiers intacts, la fusion marquée déposée à côté |
| Ce que ça coûte au dépôt | rien de plus | chaque fichier généré est committé deux fois |
Les deux dernières lignes sont le vrai arbitrage. Crabster refuse de planter des marqueurs dans un fichier qu'il n'a pas écrit — un projet qui n'est pas dans git n'aurait alors aucun moyen de revenir en arrière — et il paie cette indépendance vis-à-vis de git par une copie committée du code généré.
C'est ADR-0002 appliquée telle qu'elle était écrite. Elle écarte explicitement la mécanique de JHipster :
Alternative écartée : régénération complète avec
git diffmanuel laissé à l'utilisateur — simple à implémenter, mais reporte tout le coût sur l'utilisateur et ne tient pas à l'échelle d'un projet réel.
Le point d'histoire vaut d'être gardé : cette section disait, il y a peu, que
JHipster avait une réponse imparfaite depuis des années et Crabster aucune. Le
raisonnement qui repoussait crabster upgrade était faux — il supposait qu'un
instantané était un prérequis de la commande, alors que l'empreinte suffisait à
répondre à la seule question qui la bloquait. L'instantané est venu ensuite, et
il n'apporte que la fusion.
Là où Crabster est devant🔗
Quatre choses, et il faut les dire aussi honnêtement que le reste.
Il sait ce que vous avez touché. Chaque fichier généré porte une empreinte.
JHipster régénère tout et laisse git trancher — il ne sait pas distinguer votre
travail du sien. Crabster refuse d'écraser et nomme le fichier, aussi bien sur
record que sur upgrade, et c'est aussi ce qui lui permet de ne fusionner que
là où il y a quelque chose à fusionner.
Le contrat d'erreur. RFC 9457 pour toute défaillance, avec un type que le
projet sert lui-même, et un document OpenAPI généré depuis les handlers — donc
incapable de dériver du code. C'est plus serré que la moyenne du genre.
Le verrouillage optimiste. JHipster ne génère pas de @Version : les
tickets le demandant sont ouverts depuis 2015, et c'était jusqu'à peu un manque
partagé — les deux outils laissaient deux écritures concurrentes s'écraser en
silence. @versioned le ferme : ETag en lecture, If-Match obligatoire en
écriture, 412 sur une version dépassée, 428 sur une écriture qui ne dit
rien, et la vérification dans la même instruction SQL que l'écriture.
Pas de bibliothèque de support. Le projet généré ne dépend d'aucun runtime
Crabster : ni io.github.jhipster, ni rien. Arrêter d'utiliser l'outil ne coûte
rien.
Ce que j'en reprendrais🔗
Cette liste était celle des choses à rattraper. Elle est vide, et c'est en soi un résultat à dater : les quatre entrées sont faites.
Les objets liés dans les réponses— fait,?expand=.Les opérateurs de filtre— faits, saufin, qui attend quelqu'un qui en ait besoin plutôt qu'une ligne de plus dans un tableau.Une réponse de secours à la montée de version, même laide— sans objet : la vraie fusion 3-voies est arrivée avant que la version laide ne soit écrite.Le verrouillage optimiste— fait, depuis@versioned. C'était un endroit où prendre l'avance plutôt qu'à rattraper.
Ce qui reste à leur prendre n'est donc pas une fonctionnalité, c'est une mécanique d'écosystème : un blueprint capable de remplacer un sous-générateur entier, et pas seulement un jeu de gabarits. Tant que personne n'écrit de blueprint hors de ce dépôt, la question reste théorique — et c'est exactement ce que mesure la phase 18.
Et ce que je ne reprendrais pas : devDatabaseType.
Sources🔗
- Upgrading an application — JHipster
- JDL Options — JHipster
- Filtering your entities — JHipster
- Using Elasticsearch — JHipster
@Versionfor JPA entities — issue #1764- Optimistic locking — issue #1074
- Needle API, côté serveur
application_options.js— jhipster-core- Only 'h2Memory', 'h2Disk', 'mysql' are allowed as devDatabaseType — issue #12643 {% endraw %}