Sequelize.js: comment utiliser les migrations et la synchronisation


137

Je suis sur le point d'avoir mon projet prêt à démarrer. J'ai de grands projets pour après le lancement et la structure de la base de données va changer - de nouvelles colonnes dans les tables existantes ainsi que de nouvelles tables, et de nouvelles associations à des modèles existants et nouveaux.

Je n'ai pas encore touché aux migrations dans Sequelize, car je n'ai eu que des données de test que je ne crains pas d'effacer à chaque fois que la base de données change.

À cette fin, j'exécute actuellement sync force: truelorsque mon application démarre, si j'ai changé les définitions de modèle. Cela supprime toutes les tables et les crée à partir de zéro. Je pourrais omettre l' forceoption de ne créer que de nouvelles tables. Mais si ceux existants ont changé, cela n'est pas utile.

Donc, une fois que j'ajoute des migrations, comment les choses fonctionnent-elles? Évidemment, je ne veux pas que les tables existantes (contenant des données) soient effacées, c'est donc sync force: truehors de question. Sur d'autres applications que j'ai aidé à développer (Laravel et autres frameworks) dans le cadre de la procédure de déploiement de l'application, nous exécutons la commande migrate pour exécuter toutes les migrations en attente. Mais dans ces applications, la toute première migration a une base de données squelette, avec la base de données dans l'état où elle était au début du développement - la première version alpha ou autre. Ainsi, même une instance de l'application en retard à la fête peut se mettre à niveau en une seule fois, en exécutant toutes les migrations dans l'ordre.

Comment générer une telle "première migration" dans Sequelize? Si je n'en ai pas, une nouvelle instance de l'application en aval de la ligne n'aura pas de base de données squelette sur laquelle exécuter les migrations, ou elle se synchronisera au début et rendra la base de données dans le nouvel état avec tous les nouvelles tables, etc., mais quand il essaie d'exécuter les migrations, elles n'auront aucun sens, car elles ont été écrites avec la base de données d'origine et chaque itération successive à l'esprit.

Ma réflexion: à chaque étape, la base de données initiale plus chaque migration en séquence doit être égale (plus ou moins de données) à la base de données générée lorsque sync force: trueest exécuté. En effet, les descriptions de modèle dans le code décrivent la structure de la base de données. Alors peut-être que s'il n'y a pas de table de migration, nous exécutons simplement la synchronisation et marquons toutes les migrations comme terminées, même si elles n'ont pas été exécutées. Est-ce ce que je dois faire (comment?), Ou Sequelize est-il censé le faire lui-même, ou est-ce que je suis en train d'aboyer le mauvais arbre? Et si je suis dans le bon domaine, il devrait sûrement y avoir un bon moyen de générer automatiquement la majeure partie d'une migration, étant donné les anciens modèles (par commit hash? Ou même chaque migration pourrait-elle être liée à un commit? Je concède que je pense dans un univers non portable centré sur git) et les nouveaux modèles. Il peut modifier la structure et générer les commandes nécessaires pour transformer la base de données d'ancienne en nouvelle, et inversement, puis le développeur peut entrer et apporter les modifications nécessaires (suppression / transition de données particulières, etc.).

Lorsque j'exécute le binaire sequelize avec la --initcommande, il me donne un répertoire de migrations vide. Quand je l'exécute, sequelize --migratecela me fait une table SequelizeMeta avec rien dedans, pas d'autres tables. Evidemment non, car ce binaire ne sait pas comment démarrer mon application et charger les modèles.

J'ai dû louper quelque chose.

TLDR: comment configurer mon application et ses migrations afin que diverses instances de l'application en direct puissent être mises à jour, ainsi qu'une toute nouvelle application sans base de données de départ héritée?


2
J'ai répondu concernant votre flux de travail, mais idéalement, toutes les tables doivent être configurées à l'aide de migrations. Même si vous utilisez syncpour le moment, l'idée est que les migrations "génèrent" la base de données entière, donc s'appuyer sur un squelette est en soi un problème. Le flux de travail Ruby on Rails, par exemple, utilise Migrations pour tout, et c'est assez génial une fois que vous vous y êtes habitué. Edit: Et oui, j'ai remarqué que cette question est assez ancienne, mais comme il n'y a jamais eu de réponse satisfaisante et que les gens peuvent venir ici pour chercher des conseils, j'ai pensé que je devrais contribuer.
Fernando Cordeiro

Réponses:


88

Générer la "première migration"

Dans votre cas, le moyen le plus fiable est de le faire presque manuellement. Je suggérerais d'utiliser l' outil sequelize-cli . La syntaxe est plutôt simple:

sequelize init
...
sequelize model:create --name User --attributes first_name:string,last_name:string,bio:text

Cela créera à la fois le modèle ET la migration. Ensuite, fusionnez manuellement vos modèles existants avec ceux générés avec sequelize-cli, et faites de même avec les migrations. Après cela, effacez la base de données (si possible) et exécutez

sequelize db:migrate

Cela créera des migrations de schéma. Vous ne devez le faire qu'une seule fois pour passer au processus approprié de développement de schéma (sans sync: force, mais avec des migrations faisant autorité).

Plus tard, lorsque vous aurez besoin de changer de schéma:

  1. Créez une migration: sequelize migration:create
  2. Notez les fonctions dans votre fichier de migration
  3. En fonction de vos modifications dans le fichier de migration, modifiez votre modèle manuellement
  4. Courir sequelize db:migrate

Exécution de migrations en production

De toute évidence, vous ne pouvez pas effectuer de ssh vers le serveur de production et exécuter des migrations manuellement. Utilisez umzug , outil de migration indépendant du framework pour Node.JS pour effectuer des migrations en attente avant le démarrage de l'application.

Vous pouvez obtenir une liste des migrations en attente / non encore exécutées comme ceci:

umzug.pending().then(function (migrations) {
  // "migrations" will be an Array with the names of
  // pending migrations.
}); 

Puis exécutez les migrations ( callback interne ). La méthode execute est une fonction à usage général qui exécute pour chaque migration spécifiée la fonction respective:

umzug.execute({
  migrations: ['some-id', 'some-other-id'],
  method: 'up'
}).then(function (migrations) {
  // "migrations" will be an Array of all executed/reverted migrations.
});

Et ma suggestion est de le faire avant le démarrage de l'application et d'essayer de desservir les itinéraires à chaque fois. Quelque chose comme ça:

umzug.pending().then(function(migrations) {
    // "migrations" will be an Array with the names of
    // pending migrations.
    umzug.execute({
        migrations: migrations,
        method: 'up'
    }).then(function(migrations) {
        // "migrations" will be an Array of all executed/reverted migrations.
        // start the server
        app.listen(3000);
        // do your stuff
    });
});

Je ne peux pas essayer cela pour le moment, mais à première vue, cela devrait fonctionner.

UPD avril 2016

Au bout d'un an, toujours utile, donc partager mes conseils actuels. Pour l' instant, j'installe sequelize-clipaquet au besoin en direct dépendance, puis modifier les scripts de NPM package.jsoncomme ceci:

...
"scripts": {
  "dev": "grunt && sequelize db:migrate && sequelize db:seed:all && node bin/www",
  "start": "sequelize db:migrate && sequelize db:seed:all && node bin/www"
},
...

La seule chose que je dois faire sur le serveur de production est npm start. Cette commande exécutera toutes les migrations, appliquera tous les seeders et démarrera le serveur d'applications. Pas besoin d'appeler umzug manuellement.


3
Cela ressemble à ce que je recherche. Cela ne semble pas aussi magique et automatique que cela "devrait" l'être, mais c'est peut-être le meilleur que l'on puisse espérer. Cependant, je ne travaille actuellement pas avec Sequelize et je ne pourrai pas le tester de si tôt. Mais si quelqu'un d'autre convient que cette solution est bonne, j'accepterai cette réponse. Je trouve toujours un peu triste qu'il semble n'y avoir aucun moyen d'effectuer automatiquement ces migrations à partir des différences entre les versions de modèle.
tremby

4
@tremby le seul framework que j'ai utilisé qui comprend vraiment les modèles était Django. Il analyse les modèles et demande comme "Eh bien, il semble que vous avez renommé le nom du champ en prénom dans le modèle Utilisateur. Souhaitez-vous créer une migration pour celui-ci?" Dans Django, cela fonctionne presque comme par magie, d'autres outils que j'ai utilisés supposent la même approche de migration que j'ai mentionnée ci-dessus: vous êtes responsable de l'écriture des migrations vous-même, en comprenant profondément quel champ et quel type ajouter pour être réel à vos états de modèle actuels
f1nn

2
Vous pouvez vous débarrasser de pendingpuis executeet juste faire umzug.up().then(function (migrations) { app.listen(3000); }). Selon la documentation umzug, cela exécutera toutes les migrations en attente.
Vinay

Lorsque vous avez terminé la migration, est-il courant d'ajouter les champs au schéma dans le fichier de modèle d'origine?
theptrk

@ f1nn J'ai une question sur votre configuration, comment gérez-vous le clustering et la disponibilité des applications? J'intégrerai pm2 dans mon flux de travail et peut-être que cela ne fonctionnera pas directement avec les scripts npm.
diosney

17

Je viens d'apprendre cela moi-même, mais je pense que je recommanderais d'utiliser les migrations maintenant pour que vous vous y habituiez. J'ai trouvé que la meilleure chose pour comprendre ce qui se passe dans la migration est de regarder le sql sur les tables créées par sequelize.sync(), puis de créer les migrations à partir de là.

migrations -c [migration name] 

Créera le fichier de migration de modèle dans un répertoire de migrations. Vous pouvez ensuite le remplir avec les champs dont vous avez besoin. Ce fichier devra inclure createdAt/ updatedAt, les champs nécessaires aux associations, etc.

Pour la création initiale de la table, down devrait avoir:

migration.dropTable('MyTable');

Mais les mises à jour ultérieures de la structure de la table peuvent laisser cela de côté et utiliser simplement alter table.

./node_modules/.bin/sequelize --migrate

Un exemple de création ressemblerait à ceci:

module.exports = {
  up: function(migration, DataTypes, done) {
    migration.createTable(
        'MyTable',
        {
          id: {
            type: DataTypes.INTEGER,
            primaryKey: true,
            autoIncrement: true
          },
          bigString: {type: DataTypes.TEXT, allowNull: false},
          MyOtherTableId: DataTypes.INTEGER,
          createdAt: {
            type: DataTypes.DATE
          },
          updatedAt: {
            type: DataTypes.DATE
          }
        });
    done();
  },
  down: function(migration, DataTypes, done) {
    migration.dropTable('MyTable');
    done();
  }

Pour refaire depuis le début:

./node_modules/.bin/sequelize --migrate --undo
./node_modules/.bin/sequelize --migrate

J'utilise du café pour exécuter un fichier de départ pour remplir les tables après:

coffee server/seed.coffee

Cela a juste une fonction de création qui ressemble à quelque chose comme:

user = db.User.create
  username: 'bob'
  password: 'suruncle'
  email: 'bob@bob.com'
.success (user) ->
  console.log 'added user'
  user_id = user.id
  myTable = [
    field1: 'womp'
    field2: 'rat'

    subModel: [
      field1: 'womp'
     ,
      field1: 'rat'
    ]
  ]

N'oubliez pas de retirer votre sync()index dans vos modèles, sinon il écrasera ce que font les migrations et les semences.

Les documents sont à http://sequelize.readthedocs.org/en/latest/docs/migrations/ bien sûr. Mais la réponse de base est que vous devez tout ajouter en vous-même pour spécifier les champs dont vous avez besoin. Cela ne le fait pas pour vous.


5
Je ne demandais pas comment créer et exécuter des migrations - comme vous l'avez souligné, tout est disponible dans la documentation. Ce que je demandais, c'est comment les utiliser dans le contexte d'une application réutilisable où les instances existantes doivent être mises à jour vers une version de base de données plus récente et les nouvelles instances ont besoin de cette base de données créée à partir de zéro. Ou peut - être vous êtes répondez que, et en disant que je ne devrait pas utiliser du tout de synchronisation (), et faire la base de données initiale et toutes ses modifications dans les migrations. C'est ce que vous dites?
tremby

1
@tremby Je pense que c'est ce qu'il dit. Vous pouvez soit utiliser la synchronisation et gérer les résultats, soit créer les migrations toutes manuellement. Nos frameworks, à la manière de Rails, génèrent des fichiers de migration basés sur un schéma diff, j'adorerais si Sequelize le ferait pour moi. Trop de douleur pour faire des migrations manuellement ...
mpowered

C'est dommage que vous ne puissiez pas sequelize.sync()avoir un script généré qui crée toutes les tables de base et tous les index lors de votre première migration (similaire à rails ' schema.rb.) Après avoir lu ceci, il semble que votre meilleur pari pourrait être d'exporter votre schéma initial en tant que sql, puis mettez-le dans une grande execdéclaration lors de votre toute première migration. Ensuite, à partir de là, vous exécutez des modifications incrémentielles par rapport à un point de départ connu de la «version 1.0».
thom_nic

11

Pour le développement , il existe maintenant une option pour synchroniser les tables actuelles en modifiant leur structure. En utilisant la dernière version du repo github sequelize , vous pouvez maintenant exécuter la synchronisation avec le alterparamètre.

Table.sync({alter: true})

Une mise en garde de la documentation:

Modifie les tableaux pour s'adapter aux modèles. Non recommandé pour une utilisation en production. Supprime les données des colonnes qui ont été supprimées ou dont le type a été modifié dans le modèle.


3

Désormais, avec la nouvelle migration sequelize, c'est très simple.

Voici un exemple de ce que vous pouvez faire.

    'use strict';

    var Promise = require('bluebird'),
        fs = require('fs');

    module.exports = {
        up: function (queryInterface, Sequelize) {

            return Promise
                .resolve()
                .then(function() {
                    return fs.readFileSync(__dirname + '/../initial-db.sql', 'utf-8');
                })
                .then(function (initialSchema) {
                    return queryInterface.sequelize.query(initialSchema);
                })
        },

        down: function (queryInterface, Sequelize) {
            return Promise
                .resolve()
                .then(function() {
                    return fs.readFileSync(__dirname + '/../drop-initial-db.sql', 'utf-8');
                })
                .then(function (dropSql) {
                    return queryInterface.sequelize.query(dropSql);
                });
        }
    };

N'oubliez pas que vous devez définir:

"dialectOptions": { "multipleStatements": true }

sur la configuration de la base de données.


Cela ne fait-il pas que supprimer et recréer la base de données?
TWilly

Je pense que l'utilisation d'un grand fichier sql initial n'est pas la manière recommandée de le faire, car elle liera l'adaptateur et la base de données, qui autrement seront indépendantes de la base de données, puisque vous pouvez utiliser pour le développement sqlite et pour la production mariadb ou autre.
diosney

2

Utilisez la version. La version de l'application dépend de la version de la base de données. Si la nouvelle version nécessite une mise à jour d'une base de données, créez une migration pour celle-ci.

mise à jour: j'ai décidé d'abandonner la migration ( KISS ) et d'exécuter le script update_db (sync forse: false) quand cela est nécessaire.


Semblable à ma réponse à la réponse de user1916988, dites-vous que je ne devrais pas utiliser sync()du tout et que j'ai besoin d'écrire manuellement les migrations du schéma des modèles de l'ancienne version vers les modèles de la nouvelle version?
tremby

J'ai attribué +1 à cause de votre mise à jour. Je pense en fait à faire de même. Ecrire toutes les migrations manuellement lorsque l'application peut le faire est un peu stupide, alors je vais juste faire un script manuel qui exécute l'application une fois et exécute la fonction de synchronisation.
Sallar

2

Un peu tard, et après avoir lu la documentation, vous n'avez pas besoin d'avoir cette première migration dont vous parlez. Tout ce que vous avez à faire est d'appeler syncpour créer les tables.

sequelize.sync()

Vous pouvez également exécuter une synchronisation de modèle simple en faisant quelque chose comme:

Project.sync()mais je pense que sequelize.sync()c'est un cas général plus utile pour votre projet (tant que vous importez les bons modèles au moment du démarrage).

(extrait de http://sequelizejs.com/docs/latest/models#database-synchronization )

Cela créera toutes les structures initiales . Ensuite, vous n'aurez plus qu'à créer des migrations pour faire évoluer vos schémas.

J'espère que ça aide.


7
Je ne pense pas que vous ayez lu le message original très attentivement, ou peut-être que je n'étais pas assez clair. Je suis plus que conscient de sequelize.sync()ce qu'il fait.
tremby

2

Sequelize peut exécuter du SQL arbitraire de manière asynchrone .

Ce que je ferais, c'est:

  • Générer une migration (à utiliser comme première migration);
  • Videz votre base de données, quelque chose comme: mysql_dump -uUSER -pPASS DBNAME > FILE.SQL
  • Collez le vidage complet sous forme de texte (dangereux) ou chargez un fichier avec le vidage complet dans Node:
    • var baseSQL = "LOTS OF SQL and it's EVIL because you gotta put \ backslashes before line breakes and \"quotes\" and/or sum" + " one string for each line, or everything will break";
    • var baseSQL = fs.readFileSync('../seed/baseDump.sql');
  • Exécutez ce vidage sur Sequelize Migration:
module.exports = {
  up: function (migration, DataTypes) {
    var baseSQL = "whatever" // I recommend loading a file
    migration.migrator.sequelize.query(baseSQL);
  }
}

Cela devrait prendre en charge la configuration de la base de données, bien que la chose asynchrone puisse devenir un problème. Si cela se produit, je chercherais un moyen de différer le retour de la upfonction sequelize jusqu'à l'asyncquery fonction soit terminée.

En savoir plus sur mysql_dump: http://dev.mysql.com/doc/refman/5.1/en/mysqldump.html En
savoir plus sur Sequelize Migrations: http://sequelize.readthedocs.org/en/latest/docs/migrations/ En
savoir plus Exécution de SQL depuis Sequelize Migration: https://github.com/sequelize/sequelize/issues/313


1

Voici mon flux de travail actuel. Je suis ouvert aux suggestions.

  1. Définissez sequelize pour créer des tables qui n'existent pas
  2. Définissez sequelize pour supprimer et recréer toutes les tables dans une base de données vide appelée _blank
  3. Utilisez un outil mysql pour comparer _blank et et synchroniser les modifications à l'aide de cet outil. Toujours à la recherche d'un outil abordable capable de le faire sur mac. MySql workbench semble que vous pouvez importer un modèle à partir d'un schéma existant, puis synchroniser le schéma. Essayer de comprendre comment faire cela via la ligne de commande pour le rendre facile.

De cette façon, vous n'avez pas à mettre à jour manuellement la table des migrations et à vous soucier des gros doigts, mais vous obtenez toujours un ORM.


1

Ami, j'ai eu la même question et j'ai réussi à comprendre comment les utiliser.

J'ai commencé sans séquelle ORM donc j'avais déjà un modèle de données.
J'ai dû générer les modèles automatiquement avec sequelize-auto et générer leurs migrations avec ce fichier que vous créez https://gist.github.com/ahelord/a7a7d293695b71aadf04157f0f7dee64 et que vous mettez en synchronisation ( {Force: false})
C'est en dev, je devrais la version le modèle et les migrations et les exécuter à chaque fois que je tire le code.

En production, le serveur n'est qu'à l'étage, vous n'avez donc qu'à exécuter des migrations et à chaque commit, gérez comme vous allez versionner le modèle sans arrêter le backend


1

J'ai parcouru ce post et des questions similaires, cela ne m'a pas vraiment répondu. Les migrations sont utiles pour lancer des bases de données locales et pour mettre à jour des données en production

J'ai posé la question ici et y ai également répondu: Workflow pour gérer les migrations de séquelles et l'initialisation?

Version TL-DR pour un projet greenfield

  1. Concevez votre schéma de base de données comme vous le feriez traditionnellement en utilisant des scripts SQL purs ou si vous utilisez plutôt un outil d'interface graphique
  2. Lorsque vous avez finalisé tous vos 95% de votre schéma de base de données et que vous en êtes satisfait, allez-y et déplacez-le vers la séquelle en déplaçant le .sqlfichier entier
  3. Faites votre première migration. Exécutez sequelize init:migratedans le dossier où vous vous modelstrouvez
  4. Créez votre premier fichier de migrations. Courirsequelize migration:generate --name [name_of_your_migration]
  5. Dans ce fichier de migration, mettez ce code là-dedans
("use strict");
/**
 * DROP SCHEMA public CASCADE; CREATE SCHEMA public
 * ^ there's a schema file with all the tables in there. it drops all of that, recreates
 */
const fs = require("fs");
const initialSqlScript = fs.readFileSync("./migrations/sql/Production001.sql", {
  encoding: "utf-8",
});
const db = require("../models");
module.exports = {
  up: () => db.sequelize.query(initialSqlScript),
  down: () =>
    db.sequelize.query(`DROP SCHEMA public CASCADE; CREATE SCHEMA public;
`),
};

entrez la description de l'image ici

avec cette structure générale de dossiers

entrez la description de l'image ici

  1. Maintenant, votre configuration sequelize est synchronisée avec votre schéma de base de données initial
  2. lorsque vous souhaitez modifier votre schéma de base de données, exécutez-le à nouveau sequelize migration:generate --name [name_of_your_migration]
  3. Allez-y et apportez vos modifications ici sur les chemins de migration upet down. Ce sont vos instructions ALTER pour changer les noms de colonne, DELETE, ADD colonnes, etc.
  4. Courir sequelize db:migrate
  5. Vous voulez que les modèles soient synchronisés avec les modifications apportées à votre base de données distante, alors ce que vous pouvez faire maintenant est npm install sequelize-auto.
  6. Cela lira le schéma de base de données actuel sur votre base de données et générera automatiquement les fichiers de modèle. Utilisez une commande similaire à celle sequelize-auto -o "./models" -d sequelize_auto_test -h localhost -u my_username -p 5432 -x my_password -e postgrestrouvée sous https://github.com/sequelize/sequelize-auto

Vous pouvez utiliser git pour voir les difflogs sur votre modèle, il ne devrait y avoir que des changements reflétant les changements dans le modèle de base de données. En remarque, ne modifiez jamais le modelsdirectement si vous utilisez sequelize auto, car cela les générera pour vous. De même, vous ne devez plus modifier le schéma de votre base de données directement avec les fichiers SQL, à condition qu'il s'agisse d'une option car vous pouvez les importer.sql fichiers

Maintenant, votre schéma de base de données est à jour et vous êtes officiellement passé à la séquelle des migrations de bases de données uniquement.

Tout est contrôlé par version. C'est le flux de travail idéal pour les développeurs de bases de données et de backend


0

Il existe un moyen encore plus simple (éviter Sequalize). Ce qui va comme ceci:

  1. Vous tapez une commande dans votre projet: npm run migrate: new

  2. Cela crée 3 fichiers. Un fichier js et deux fichiers sql nommés haut et bas

  3. Vous mettez votre instruction SQL dans ces fichiers, qui est pur SQL
  4. Ensuite, vous tapez: npm run migrate: up ou npm run migrate: down

Pour que cela fonctionne, veuillez consulter le module db-migrate .

Une fois que vous l'avez configuré (ce qui n'est pas difficile), changer votre base de données est vraiment facile et vous fait gagner beaucoup de temps.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.