La Frond
Une Frond est un module métier qui regroupe les éléments responsables d'un même domaine.
La frontière de propriété
Une Frond contient :
- ses entités et schémas,
- ses opérations — commands et queries (handlers),
- ses policies — les règles d'accès vérifiées dans les opérations,
- ses events — les faits que ses commands produisent (un bus in-process, auquel rien ne s'abonne par défaut),
- ses adapters de persistance — comment ses entités se réalisent,
- ses contrats publics — ce que les autres Fronds peuvent appeler,
- ses règles d'invalidation — ce qu'une command revalide.
Une Frond n'importe pas les fichiers internes d'une autre. Les interactions passent par des contrats explicites, de préférence des opérations ou des events. Une App compose les Fronds et conserve la logique métier dans ces modules plutôt que dans ses pages, fetchs ou stores.
Les six couches
Le modèle suit six étapes :
| # | Couche | Ce qui s'y passe |
|---|---|---|
| 1 | Déclaration | l'auteur déclare entités, vues, opérations, events — ni HTTP ni SQL ici |
| 2 | Dérivation | validation, types, vues io, metadata lisible par les adapters en dérivent |
| 3 | Capacité locale | la déclaration devient exécutable in-process — le mode de référence |
| 4 | Projection | des façades sont générées vers les transports : HTTP, GraphQL, CLI, workers |
| 5 | Composition | les Fronds sont scannées et reliées dans un runtime — module → contrat → interaction |
| 6 | Distribution | une capacité part hors process ; sérialisation, timeouts, retries apparaissent ici, explicitement |
La page sur le gradient décrit le passage de l'exécution locale à l'exécution distante.
Généré vs écrit à la main
La répartition est la suivante :
| Fougere génère | Vous écrivez |
|---|---|
| validation, vues dérivées, tables/metadata | la logique métier non triviale |
| types et inputs GraphQL, CRUD trivial | les invariants riches, les workflows multi-étapes |
| les façades de projection (contrat → transport) | les politiques d'erreur, les décisions de cohérence |
| les clients typés, les manifests de composition | l'orchestration inter-Fronds sensible |
Fougere prend en charge les intégrations répétitives autour d'un contrat. Le développeur écrit le comportement propre à l'application. Une couche service reste optionnelle : un handler peut contenir la logique ou la déléguer.
Dans le système de fichiers
fronds/blog/
entities/ ← les déclarations
handlers/ ← les opérations (handlers/<surface>/ = une audience nommée)
presenters/ ← les champs calculés ajoutés à la sortie d'une entité
collectors/ ← la résolution de paramètres par type
services/ ← des classes ordinaires, injectées par type
repositories/ ← la même chose, sous un second nom
seeds/ ← les données créées au démarrage
Le scanner reconnaît une Frond à ces dossiers. La racine du projet peut suivre la
même convention : une app avec un seul domaine n'a alors pas besoin de répertoire
fronds/ (la forme à plat).
Un deuxième domaine peut ensuite être ajouté sous fronds/ sans déplacer le premier.
Nommer et importer une Frond
Le nom du dossier est le nom de la Frond, et @fougere/nuxt en fait un alias :
fronds/blog/ donne @frond/blog, donc une page écrit
import Post from '@frond/blog/entities/Post' sans aucun fichier à ajouter. Ce nom sert
aussi de clé aux enregistrements d'entités et à toute entrée remotes:.
Un package.json dans la Frond répond aux deux questions que le dossier ne sait pas dire.
{
"name": "@frond/blog",
"fougere": { "frond": "blog" },
"exports": { "./entities/*": "./entities/*.ts" }
}
fougere.frond permet d'utiliser un nom différent de celui du dossier. Par exemple,
fronds/blog-v2/ peut rester enregistrée sous le nom blog, ce qui conserve ses clés
d'entité, ses imports @frond/* et ses entrées remotes:. Sans cette propriété, Fougere
utilise le nom du dossier.
name et exports rendent la Frond résolvable en dehors d'une app Nuxt — un script
Node, un consommateur non-Nuxt — ce qui demande aussi de la déclarer comme paquet du
workspace (packages: [fronds/*] dans pnpm-workspace.yaml). Dans une app Nuxt, l'alias
suffit.
services/ et repositories/ se comportent à l'identique : les deux enregistrent leurs
classes comme providers, résolus par type. La plupart des accès data n'ont besoin ni de
l'un ni de l'autre — le handler reçoit déjà un EntityOrm scopé à son entité. On prend un
provider quand la lecture ne rentre pas dans cette forme : un agrégat sur plusieurs
entités, une source qui n'est pas un ORM (une API externe, un index de recherche), ou une
requête assez lourde pour que le handler n'ait pas à la porter.