Accès aux données¶
Les services accèdent à la base via des accesseurs Sequelize typés exportés depuis database/sequelize/models.ts. Chaque entité de domaine possède un fromRecord(attrs) statique qui mappe un record Sequelize vers une entité de domaine.
Accesseurs typés et fromRecord¶
import { Workspaces } from '../../database/sequelize/models';
import { Workspace } from '../../domain/workspaces/workspace.model';
const record = await Workspaces.findByPk(id);
if (!record) throw new NotFoundError();
return Workspace.fromRecord(record.get());
findByPk/findOnerenvoientnullen cas d'absence → le service vérifie explicitement et lèveNotFoundError(voir Erreurs & i18n).- L'interface d'attributs est exportée à côté de la classe de domaine.
Jamais de .catch(() => null)
Cela masquerait des pannes d'infrastructure. On ne capture que les erreurs de domaine attendues, et on relance les erreurs inattendues.
Lecture relationnelle optimisée avec include¶
Préférez include à un N+1 dès qu'un chemin de lecture fait « requête A, puis recherche B par les IDs de A » :
const memberships = await WorkspaceMemberships.findAll({
where: { userId: user.id },
include: [{ model: Workspaces }],
});
const workspaces = memberships
.map((m) => m.get('Workspace') as WorkspaceModel | undefined)
.filter((w): w is WorkspaceModel => w != null)
.map((w) => Workspace.fromRecord(w.get()));
Cast d'include
Le cast de record.get('User') est as Entity pour un INNER JOIN, ou as Entity | null pour un LEFT JOIN sur une FK nullable. Jamais | undefined.
Chaque FK d'un modèle doit avoir une paire belongsTo / hasMany correspondante (voir Associations), sinon include lève une EagerLoadingError.
Invariants de colonnes¶
- Ne pas écrire
?? defaultsur une lecture Sequelize quand la colonne estallowNull: false: on fait confiance à l'invariant de la base. - Les colonnes texte optionnelles sont
allowNull: false, defaultValue: ''; le type du champ de domaine eststring, jamaisstring | null.
Modèles, migrations, associations¶
| Élément | Emplacement |
|---|---|
| Modèles | database/sequelize/models/<entity>.model.ts (factories auto-chargées au démarrage). |
| Accesseurs typés | database/sequelize/models.ts — ajoutez-y une entrée à chaque nouveau modèle. |
| Associations | database/sequelize/sequelize.ts, dans buildAssociations() (jamais dans les fichiers de modèles). |
| Migrations | database/sequelize/migrations/ — scaffolder avec bash scripts/makemigrations.sh <name>. |
- Les IDs sont des ULID (26 caractères, triables) stockés en
CHAR(26). - En dev/test la base est SQLite ; en prod, PostgreSQL.
Migrations en vol (v2)
Tant que v2 est en cours, les changements de schéma de tables existantes vont dans le fichier de migration d'origine, en place — pas dans une migration de suivi. Les tables entièrement nouvelles obtiennent leur propre migration. Voir Guidelines Git.
Pour aller plus loin¶
La compétence ikloze-define-model du dépôt détaille le scaffolding de la couche de persistance (définition de modèle, migration, entité de domaine avec fromRecord + interface d'attributs, accesseur typé, associations).