Architecture
ISNAD — Règles de dépendance entre couches (E11-T2)
Sur cette page
Complément de repository-structure.md.
Aucune règle ci-dessous n'est appliquée par un outil automatisé pour
l'instant (pas d'ESLint boundary rule, pas de madge/dependency-cruiser
configuré) — ce document est la spécification à partir de laquelle un
tel outil pourra être configuré dans une micro-tâche E11 ultérieure.
Graphe autorisé#
┌────────────┐
│ app │ (Next.js App Router)
└─────┬──────┘
│
┌──────────────┼───────────────┐
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌───────────┐
│ features│ │ server │ │ shared │
└────┬────┘ └────┬─────┘ └─────┬─────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌───────────────┐
│ domain │◄───┤ (types) │ │ core / lib │
└────┬────┘ └──────────┘ └───────────────┘
│
▼
┌────────────────┐
│ infrastructure │ (implémente les ports définis dans domain/repositories)
└────────────────┘
Lecture du graphe : une flèche A → B signifie « A peut importer B ».
infrastructure pointe vers domain parce qu'elle implémente les
interfaces qui y sont définies (dépendance d'implémentation, pas d'import
au sens runtime dans l'autre sens — domain n'importe jamais
infrastructure).
Règles par couche#
app/#
- Peut importer :
features/*,domain/*(rarement, seulement pour des types),server/*,shared/*,core/*,lib/*,types/*. - Ne doit pas : contenir de la logique métier scientifique inline dans
un
page.tsxau-delà de l'assemblage UI — si une page fait plus que « charger des données + composer des composants », la logique doit vivre dansfeatures/<nom>/services/oudomain/. - Constat actuel : plusieurs pages (
corpus/page.tsx,statistics/page.tsx,stats/page.tsx) appellent Supabase directement — toléré aujourd'hui (taille raisonnable), mais toute complexité supplémentaire doit être extraite versserver/oufeatures/*/services/plutôt que d'épaissir la page.
features/#
- Peut importer :
domain/*,server/*(temporairement, tant qu'un portdomain/repositories/n'existe pas pour cette feature — voir D2),shared/*,core/*,lib/*,types/*, et d'autres modules de la même feature. - Ne doit pas : importer directement une autre
features/<autre-nom>/— si deux features ont besoin du même composant/service, il migre versshared/oucore/, il ne reste pas dupliqué ni importé en croix. - Ne doit pas : accéder à
infrastructure/directement en contournantdomain/repositories/— si une feature a besoin de persistance formelle, elle passe par un port domain, jamais par un adapter infrastructure importé en direct.
domain/#
- Peut importer : uniquement d'autres modules
domain/*ettypes/*(types purs, sans dépendance). - Ne doit jamais importer :
react,react-dom, tout modulenext/*,@supabase/supabase-js,pg,features/*,app/*,infrastructure/*,server/*, aucune API navigateur (fetch,localStorage,document,window). - Vérifié conforme aujourd'hui : grep exhaustif, 0 violation sur les
86 fichiers actuels de
src/domain/. - Un
domain/services/*peut recevoir une interfacedomain/repositories/*en paramètre (injection de dépendance) — cela reste autorisé car l'interface elle-même ne dépend de rien d'impur.
infrastructure/#
- Peut importer :
domain/repositories/*(pour implémenter leurs interfaces),core/*,lib/*,types/*, et des bibliothèques externes (pg,@supabase/supabase-js). - Ne doit jamais importer :
react,next/*,app/*,features/*. - Une classe
infrastructure/implémente toujours un type exporté pardomain/repositories/(ex.SupabaseHadithAggregateWriter implements HadithAggregateWriter) — si un nouvel adapter n'implémente aucune interface domain, c'est probablement duserver/, pas de l'infrastructure/(voir D2).
server/#
- Peut importer :
domain/*(types/lecture uniquement, jamais pour invoquer une règle de décision complexe qui devrait être dansdomain/services/),core/*,lib/*,types/*,pg,@supabase/supabase-js. - Ne doit jamais importer :
react, JSX — un moduleserver/retourne des données typées, jamais des éléments React. Le rendu se fait dansapp/oufeatures/*/components/. - Ne doit pas être importé depuis un composant marqué
'use client'(règle déjà respectée aujourd'hui — c'est la raison d'être de ce dossier).
shared/#
- Peut importer :
core/*,lib/*,types/*. - Ne doit jamais : dépendre d'une
features/<nom>/spécifique, ni contenir de logique métier scientifique. - Un composant ne migre vers
shared/que lorsqu'il est déjà utilisé par au moins deux features distinctes — jamais par anticipation.
core/#
- Peut importer :
lib/*,types/*. - Ne doit jamais importer :
features/*(le cœur ne connaît aucune feature spécifique — sinon ce n'est plus transverse).
lib/#
- Peut importer : uniquement d'autres modules
lib/*ettypes/*. - Ne doit jamais importer :
domain/*(le sens est strictement inverse —domain/peut en théorie utiliser une primitivelib/*très générique si nécessaire, maislib/ne remonte jamais versdomain/), nifeatures/*, ni React/Next.js pour les primitives pures (les quelques fichierslib/*.tsxcommei18n.tsx/theme.tsxsont des candidatscore/, pas des primitives — voir D7/D8).
Interdictions explicites (rappel condensé)#
domain → features, app, React, Next.js, Supabase, pg, fetch INTERDIT
shared → une feature spécifique INTERDIT
infrastructure→ app, features, React INTERDIT
server → React, JSX INTERDIT
lib → domain, features INTERDIT
core → features INTERDIT
features/X → features/Y (une autre feature) INTERDIT
Comment ces règles seront vérifiées (futur, non fait en E11-T2)#
Options envisageables pour une micro-tâche E11 ultérieure (non décidé ici, simple inventaire des options) :
dependency-cruiseroumadgeavec un fichier de règles reproduisant le graphe ci-dessus, exécuté en CI.- Règle ESLint
no-restricted-importspar dossier si l'équipe préfère rester dans l'outillage déjà présent (eslintn'est cependant pas configuré aujourd'hui dans ce repo — à vérifier lors de l'introduction de la CI, voir T1 risque « absence de CI »).
Aucun de ces outils n'est installé ou configuré par ce document.