precursor Δημοσ. 30 Ιουνίου 2018 Δημοσ. 30 Ιουνίου 2018 (επεξεργασμένο) ΣΥΝΤΟΜΗ ΠΕΡΙΓΡΑΦΗ Φτιάχνω ένα μικρό web application (ή και "website" καθώς το ένα δεν αποκλείει τελείως το άλλο) με MVC (Model View Controller) αρχιτεκτονική. Είναι γραμμένο σε Node.js (δηλ. JavaScript για backend) και χρησιμοποιεί το Express.js ως web server framework και το Sequelize ORM για το πάρε-δώσε με μια βάση PostgreSQL. Δεν έχει σημασία τι εφαρμογή είναι. Αυτό που θέλω να κρατήσετε κυρίως είναι ότι είναι εξ' ολοκλήρου φτιαγμένο με ES6 κλάσεις και ότι πρέπει να υλοποιείται όσο πιο σωστά γίνεται η αρχιτεκτονική MVC. Υπάρχει ως entry point της εφαρμογής ένα app.js που δημιουργεί instance του Express server, κάνει διάφορες ρυθμίσεις για το app, περιλαμβάνει τα routes και ακούει για requests -> έπειτα λαμβάνει κάποιο request και χτυπάει κάποιο route -> βρίσκει και κάνει require/import την αντίστοιχη κλάση τύπου controller ή οποία με τη σειρά της καλεί μια κλάση τύπου model -> που μπορεί να χρειαστεί για το τελικό render κάποιου template (δηλ. του View). ΜΙΑ ΜΑΤΙΑ ΣΤΟΝ ΚΩΔΙΚΑ (MODELS + DATABASE CONNECTION) Παράδειγμα model: Spoiler // Model Example "use strict"; const Model = require(...); class UserManager extends Model { constructor() { super(); // Get a Database object this.db = super.con; // Get a Database ORM object this.sequelize = super.con.dbdriver; // Define a model to use with the database this.model = this.defineModel(); } defineModel() { let model = this.db.defineModel("users", { ... }); return model; } // ABSTRACT CRUD OPERATIONS ... } // Export User model for use in controllers. module.exports = UserManager; To base model που κληρονομούν όλα τα μοντέλα: Spoiler "use strict"; /** * This is the Base Model. * All /models should inherit this class. */ const Database = require('../core/database'); const config = require('../../config').database; class Model { constructor() { // Fetch database details let DB_DIALECT = config.production.db_dialect; let DB_USERNAME = config.production.db_username; let DB_PASSWORD = encodeURIComponent(config.production.db_password); let DB_HOST = config.production.db_host; let DB_PORT = config.production.db_port; let DB_NAME = config.production.db_name; // Create a new database connection this.con = new Database(DB_DIALECT, DB_USERNAME, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME); } set con(object) { this._con = object; } get con() { return this._con; } } module.exports = Model; Παράδειγμα της κλάσης που κάνει τη σύνδεση με τη βάση δεδομένων: Spoiler "use strict"; const Sequelize = require("sequelize"); class Database { constructor(DB_DIALECT, DB_USERNAME, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME) { this.sequelize = new Sequelize(DB_DIALECT + "://" + DB_USERNAME + ":" + DB_PASSWORD + "@" + DB_HOST + ":" + DB_PORT + "/" + DB_NAME, { operatorsAliases: false, // disable logging; default: console.log logging: false, define: { charset: 'utf8' } }); // Successful connection or not? this.sequelize.authenticate().then(() => { console.log("\x1b[42m%s\x1b[0m", "Successfully connected to the database."); }).catch((err) => { console.log("\x1b[41m%s\x1b[0m", "Unable to connect to the database: " + err); }); } defineModel(tableName, tableStructure) { let model = this.sequelize.define(tableName, tableStructure); this.sequelize.sync() .then(() => { console.log("\x1b[42m%s\x1b[0m", "The " + tableName + " table has been successfully synced."); }) .catch((error) => { console.log("This error occured", error); }); return model; } get dbdriver() { return Sequelize; } // CRUD OPERATIONS ... } module.exports = Database; ΟΙ ΕΡΩΤΗΣΕΙΣ Με αυτά ως δεδομένα σας έχω 3 ερωτήσεις παρακάτω σχετικά με τα μοντέλα και με τον τρόπο σύνδεσης με τη βάση δεδομένων. 1) Πού πρέπει να υλοποιηθούν τα CRUD operations; Έχω διαβάσει αρκετές απόψεις και μια άποψη είναι ότι πρέπει να γίνονται στους controllers. Δεν συμφωνώ με αυτήν την πρακτική και την έχω απορρίψει, αλλά εάν εσείς συμφωνείτε θα ήθελα να ακούσω την γνώμη σας. Έχω καταλήξει στο ότι τα models καλό είναι να έχουν abstract υλοποιήσεις για CRUD και να καλούν από εκεί functions που έχουν υλοποιηθεί είτε στο base model είτε στην κλάση που κάνει τη σύνδεση με τη βάση... αν και δεν είμαι σίγουρος για το ποιο από τα δύο μέρη είναι προτιμότερο. Παραπάνω έχω παραθέσει παραδείγματα κώδικα για τα models, για το base model καθώς και για την Database κλάση. Κάποιοι λένε ότι τα models δεν πρέπει να είναι κλάσεις (???) αλλά layers (???) που χρησιμοποιούνται από τους controllers μέσω κάποιων service κλάσεων... Τι πιστεύετε ή/και τι ακολουθείτε εσείς στην πράξη; 2) Πού και πόσες φορές πρέπει να γίνει η σύνδεση με τη βάση δεδομένων;;; Όπως θα είδατε έχω μια κλάση Database για την σύνδεση με τη βάση. Κάθε φορά που κάποιος controller θέλει να γράψει ή να διαβάσει από τη βάση, καλεί το model που είναι υπεύθυνο για κάποιον συγκεκριμένο πίνακα της βάσης και δημιουργείται έτσι ένα νέο Database object δηλ. μια νέα σύνδεση με τη βάση για κάθε http request που καλεί κάποιο action ενός controller. Όταν θα τελειώσει το action και το Database object δεν θα χρειάζεται πια, θα μαρκαριστεί ως null για να το συλλέξει ο garbage collector. Αναγνωρίζω ότι θεωρείται expensive το να ανοίγεις και να κλείνεις συνδέσεις με τη βάση για κάθε request. Οπότε, πού και πώς πρέπει να γίνεται η σύνδεση με τη βάση δεδομένων;;; Για κάθε request δηλ. περίπου όπως το έκανα παραπάνω; Ή ίσως μία μόνο φορά στο app.js (και θέτοντας κάποιο pool με max connections - κάτι το οποίο δεν έχω πολυκαταλάβει πως λειτουργεί);;; 3) Το Sequelize.sync() το οποίο συγχρονίζει τα objects των μοντέλων με τους αντίστοιχους πίνακες στη βάση, πού και πόσες φορές πρέπει να τρέξει;;; Προσωπικά μου λειτουργεί μια χαρά η εφαρμογή με το να το τρέχω κάθε φορά που χρησιμοποιώ κάποιο μοντέλο μέσω κάποιου controller. Όπως θα είδατε στον κώδικα που παρέθεσα, σε κάθε request τρέχει το new Sequelize() για νέα σύνδεση με τη βάση, το Sequelize.define() για ορισμό του model και το Sequelize.sync() για τον συγχρονισμό του με τη βάση. Απ' ό,τι διάβασα όμως, τα define και sync αρκεί να τρέξουν μία μόνο φορά κατά τη διάρκεια εκτέλεσης της εφαρμογής (π.χ. στο app.js), και το ORM θα μπορεί να δουλέψει με τους πίνακες της βάσης καθ' όλη τη διάρκεια ζωής της εφαρμογής. Εάν γνωρίζετε από Sequelize και αν το συνδυάζετε με ES6 κλάσεις θα με ενδιέφερε να μου πείτε πως το χρησιμοποιείτε εσείς! Επεξ/σία 30 Ιουνίου 2018 από precursor
Predatorkill Δημοσ. 1 Ιουλίου 2018 Δημοσ. 1 Ιουλίου 2018 Ποτέ μην βαζεις ευαισθητα δεδομενα σε αρχεια οπως το config. Χρησιμοποιησε env variables, περισσοτερα εδω: https://medium.com/@maxbeatty/environment-variables-in-node-js-28e951631801 1
precursor Δημοσ. 2 Ιουλίου 2018 Μέλος Δημοσ. 2 Ιουλίου 2018 23 ώρες πριν, Predatorkill είπε Ποτέ μην βαζεις ευαισθητα δεδομενα σε αρχεια οπως το config. Χρησιμοποιησε env variables, περισσοτερα εδω: https://medium.com/@maxbeatty/environment-variables-in-node-js-28e951631801 Σ' ευχαριστώ για την απάντηση! Το γνωρίζω αυτό και συνήθως χρησιμοποιώ το dotenv package (αυτό ακριβώς που λέει στο άρθρο που υπέδειξες) γι' αυτόν το σκοπό όπου το config τραβάει τα δεδομένα αυτά από ένα αρχειάκι .env το οποίο το έχω προσθέσει στο .gitignore. Ωστόσο, δεν με απασχολεί ιδιαίτερα αυτή η πρακτική μιας και το προτζεκτάκι δεν το έχω για production ακόμα και μιας και είναι κάτι που γίνεται εύκολα refactored. Αυτό που με απασχολεί περισσότερο είναι να πετύχω μια αρκετά καλή δομή του MVC ώστε να βγάζω και να βάζω εύκολα πράγματα και να μην χρειαστεί κάποιο ριζικό refactoring στην πορεία. Με ενδιαφέρει να βρω κυρίως την απάντηση για τα πρώτα 2 από τα 3 ερωτήματά μου. Καμιά ιδέα;
precursor Δημοσ. 13 Ιουλίου 2018 Μέλος Δημοσ. 13 Ιουλίου 2018 Στις 9/7/2018 στις 12:39 ΜΜ, exarhis είπε Πολύ ψαγμενα κόλπα. 🏖️ Το εννοείς ότι σου φαίνονται ψαγμένα; Ίσως γι' αυτό δεν έχει βρεθεί ακόμα κάποιος να μου πει τη γνώμη του; Για εμένα που δεν έχω βγάλει άκρη ακόμα μου φαίνονται επίσης ψαγμένα, αλλά πίστευα ότι για κάποιον που έχει ασχοληθεί αρκετά με MVC (και έπειτα με Node.js) ότι θα είναι παιχνιδάκι. Η πρώτη ερώτηση απαιτεί γνώσεις MVC, η δεύτερη είναι πιο γενική για object-oriented web apps, και η τρίτη για κάποιον που γνωρίζει Node.js και το συγκεκριμένο ORM.
paparovic Δημοσ. 13 Ιουλίου 2018 Δημοσ. 13 Ιουλίου 2018 (επεξεργασμένο) α) Τα `router.verb` στους controllers. β) Μια φορά θα κάνεις instantiate το sequelize. Μετά το instantiation θα κάνεις `import()` τα models. Όλα αυτά σε ένα module που στο τέλος θα κάνει export το instance (ή κάποιο object που το περιέχει). Όποτε και όπου το χρειάζεσαι, θα κάνεις require το module και το node θα σου δίνει το instance (αν δεν το γνωρίζεις, το node κάνει cache τo return value των modules αυτόματα, δεν τα ξανατρέχει από το μηδέν κάθε φορά που κάνεις require). γ) Το `sync()` ΔΕΝ κάνει modify ένα table σε περίπτωση που ήδη υπάρχει (αν υποθέσουμε ότι άλλαξες το model). Μπορείς να το κάνεις force αλλά δεν το συνιστούν. Δεν χρειάζεται να το καλέσεις εσύ, βάλε το entry στο config, π.χ. sync: { alter: true }, Επεξ/σία 13 Ιουλίου 2018 από paparovic 1
precursor Δημοσ. 13 Ιουλίου 2018 Μέλος Δημοσ. 13 Ιουλίου 2018 30 λεπτά πριν, paparovic είπε α) Τα `router.verb` στους controllers. β) Μια φορά θα κάνεις instantiate το sequelize. Μετά το instantiation θα κάνεις `import()` τα models. Όλα αυτά σε ένα module που στο τέλος θα κάνει export το instance (ή κάποιο object που το περιέχει). Όποτε και όπου το χρειάζεσαι, θα κάνεις require το module και το node θα σου δίνει το instance (αν δεν το γνωρίζεις, το node κάνει cache τo return value των modules αυτόματα, δεν τα ξανατρέχει από το μηδέν κάθε φορά που κάνεις require). γ) Το `sync()` ΔΕΝ κάνει modify ένα table σε περίπτωση που ήδη υπάρχει (αν υποθέσουμε ότι άλλαξες το model). Μπορείς να το κάνεις force αλλά δεν το συνιστούν. Δεν χρειάζεται να το καλέσεις εσύ, βάλε το entry στο config, π.χ. sync: { alter: true }, α) Τα `router.verb` τα έχω σε έναν front controller όπου αφού πρώτα γίνει το match του route, έπειτα εξετάζει το URL και καλεί τον κατάλληλο controller που είναι υπεύθυνος για κάποια sections / views. Τι λες; Από εκεί και πέρα ο controller θα χρειαστεί σε κάποια action method του να χρησιμοποιήσει ένα instance κάποιας model κλάσης για να κάνει CRUD και π.χ. να διαβάσει δεδομένα από τη βάση και να τα στείλει στο template που θα κάνει render. Σωστά, πάνω-κάτω; Οπότε λοιπόν - και αν συμφωνείς με τα προηγούμενα αλλιώς διόρθωσέ με - η απορία μου εδώ είναι η εξής: αυτές οι CRUD ενέργειες υλοποιούνται στα models;;; (ή καλύτερα στις "model κλάσεις" μιας που θέλω τα πάντα να είναι ES6 κλάσεις) β) Ωραία, μια φορά λοιπόν! Εδώ μου γεννιούνται μερικές απορίες. Αρχικά, από τη στιγμή που θα κάνω μόνο μία φορά instantiate το Sequelize (αντί να το κάνω για κάθε request), φαντάζομαι ότι θα πρέπει να δώσω κάποια σημασία στη διαχείριση των συνδέσεων με τη βάση δεδομένων; Γνωρίζω ότι τόσο το Sequelize όσο και τα άλλα ORMs αλλά οι raw drivers, έχουν κάποια ρύθμιση για pool συνδέσεων. Σύμφωνα με τα docs του στο Sequelize είναι κάπως έτσι: Αναφορά σε κείμενο Sequelize will setup a connection pool on initialization so you should ideally only ever create one instance per database if you're connecting to the DB from a single process. If you're connecting to the DB from multiple processes, you'll have to create one instance per process, but each instance should have a maximum connection pool size of "max connection pool size divided by number of instances". So, if you wanted a max connection pool size of 90 and you had 3 worker processes, each process's instance should have a max connection pool size of 30. const sequelize = new Sequelize('database', 'username', 'password', { host: 'localhost', dialect: 'mysql'|'sqlite'|'postgres'|'mssql', pool: { max: 5, min: 0, acquire: 30000, idle: 10000 }, // SQLite only storage: 'path/to/database.sqlite', // http://docs.sequelizejs.com/manual/tutorial/querying.html#operators operatorsAliases: false }); Τι θα πρέπει να με απασχολεί με απλά λόγια όσον αφορά αυτό το pool; Αν καταλαβαίνω σωστά το `max: 5` σημαίνει ότι οι μέγιστες δυνατές συνδέσεις με τη βάση δεδομένων θα είναι 5 και συνεπώς ότι ο server θα υποστηρίζει μέχρι 5 παράλληλα requests (αν υποθέσουμε ότι όλα αυτά χρειάζονται τη βάση);;; Έπειτα, όσον αφορά τα models, θέλω να είναι ES6 κλάσεις όπως και όλα τα modules της εφαρμογής. (Αν και είδα κάποιους να λένε ότι τα models δεν πρέπει να είναι κλάσεις αλλά layers που χρησιμοποιούνται από τους controllers μέσω κάποιων service κλάσεων, ό,τι και αν σημαίνει αυτό...) Όλα τα modules μου - συνεπώς και τα models - απαρτίζονται από μία μόνο κλάση και κάνουν export αποκλειστικά αυτήν την κλάση, δηλ. έχουν αυτήν τη μορφή: Spoiler // Module Example "use strict"; // Imports here const someImport = require(...); class SomeClass extends SomeOtherClass { constructor() { super(); } someMethod() { } set something() { } get something() { } } module.exports = SomeClass; Οπότε, με αυτό ως δεδομένο... και αν κατάλαβα καλά αυτό που μου είπες να κάνω, θα πρέπει να φτιάξω ένα τέτοιο module που θα κάνει import ΟΛΑ τα models; Αν ναι, να του βάλω getters για το κάθε model ώστε όπου το χρειάζομαι αυτό το module να το κάνω require και έτσι να χρησιμοποιώ το model της επιλογής μου; Και κάτι τελευταίο πάνω σε αυτό. Πριν χρησιμοποιήσω το `sync()` θα πρέπει να χρησιμοποιήσω το `define()` μία φορά για το κάθε model (τουλάχιστον αυτό έχω καταλάβει), το οποίο καθορίζει μεταξύ άλλων το πως θα γράφονται τα δεδομένα στη βάση: "defines the mappings between a model object's properties and a database table's columns". Δεν ξέρω ποια είναι η σωστή πρακτική, αλλά με βολεύει να καλείται το `define()` σε μέθοδο στο κάθε μοντέλο (μιας και επιστρέφει ένα object με το οποίο μπορώ να κάνω CRUD στον πίνακα)... ... θα πρέπει στο προαναφερθές module να κάνω instantiate όλα τα models και να καλέσω αυτήν τη μέθοδό τους; γ) Βασικά, το `sync()` τι κάνει με απλά λόγια; Αυτό που έχω καταλάβει - ή τουλάχιστον έτσι νομίζω - είναι ότι μου επιτρέπει να χρησιμοποιήσω τους πίνακες της βάσης δεδομένων αφού έχω κάνει `define()` κάποιο object για τον καθένα. Μπερδεύτηκα με αυτό που είπες ότι δεν χρειάζεται να το καλέσω. Είχα την εντύπωση ότι για να μπορώ να διαβάσω/γράψω στη βάση δεδομένων, τα βήματα είναι 1) `new Sequelize()` για τη σύνδεση 2) `define()` για να ορίσω κάποιον πίνακα 3) `sync()` για να χρησιμοποιήσω τους πίνακες που όρισα. Δηλαδή δεν χρειάζεται να το καλέσω καθόλου προκειμένου να μπορώ να κάνω CRUD;;;
paparovic Δημοσ. 13 Ιουλίου 2018 Δημοσ. 13 Ιουλίου 2018 (επεξεργασμένο) α) Αν αυτό προτιμάς, ναι, το έχω δει να το κάνουν έτσι. Προσωπικά ξεχωρίζω σε διαφορετικά αρχεία το "τι είναι το model Χ του sequelize" και το "τι κάνω με τα models του sequelize". Οπότε εγώ κάνω require στον controller τα δεύτερα. β) Copy/paste από το πώς το στήνω εγώ και μετά σχόλια για το πώς θα το κάνεις εσύ: // `models/index.js` const Sequelize = require('sequelize'); const FS = require('fs'); const Path = require('path'); const config = require('../config'); const sequelize = new Sequelize( config.postgres.db, config.postgres.user, config.postgres.password, { host: config.postgres.host, dialect: 'postgres', logging: false, operatorsAliases: false, pool: { max: 5, idle: 30000, acquire: 60000, }, define: { charset: 'utf8', collate: 'utf8_general_ci', timestamps: true }, sync: { alter: true }, }, ); const db = {}; FS.readdirSync(__dirname) .filter(function (file) { return (file.indexOf(".") !== 0) && (file !== "index.js"); }) .forEach(function (file) { const model = sequelize.import(Path.join(__dirname, file)); db[model.name] = model; }); Object.keys(db).forEach(function (modelName) { if ("associate" in db[modelName]) { db[modelName].associate(db); } }); db.sequelize = sequelize; db.Sequelize = Sequelize; module.exports = db; // `models/patata.js` module.exports = function (sequelize, DataTypes) { const Patata = sequelize.define("Patata", { type: DataTypes.STRING, isActive: DataTypes.BOOLEAN, }, { tableName: 'Patata', timestamps: true, paranoid: true, updatedAt: true, }); Patata.associate = function (models) { Test.belongsTo(models.Tsanta, {foreignKey: 'tsantaId'}); }; return Patata; }; // `app.js` // ... const models = require('./models'); // ... const run = async () => { await models.sequelize[config.postgres.sync ? 'sync' : 'authenticate'](); app.use(require('./controllers')); server.listen(config.app.port, () => { logger.info('%s: listening on %s [%sms]', config.app.name, config.app.port, Date.now() - start); }); }; run(); Σε ένα directory όλα το models. Το index διαβάζει το κάθε file και το κάνει `import`. Το export του index είναι ένα wrapper object που έχει το instance και το class. Στο app κάνει require το models (index) και πριν ξεκινήσει τον express server περιμένει το instance του sequelize να κάνει την δουλειά του. Ok, για αυτό που θες εσύ, αντί να κάνεις export functions και objects όπως εγώ, κάνε τα wrap σε classes και προσάρμοσε αντίστοιχα τα require σου (και το `forEach` στο `readdirSync`, που υποθέτω θα το μετακινήσεις στον constructor). γ) Το `sync()` "φτιάχνει" τα tables στην βάση, ανάλογα με τα models σου. Έχεις εσύ 4 models και μια άδεια βάση. Κάνεις instanciate το sequelize με τα models και μόνο του πάει και κάνει την δουλειά του (αν έχεις βάλει το sync true στο config). Δεν κάνει crud, απλώς σου ετοιμάζει την βάση για να κάνεις το crud εσύ. Επεξ/σία 13 Ιουλίου 2018 από paparovic 1
precursor Δημοσ. 14 Ιουλίου 2018 Μέλος Δημοσ. 14 Ιουλίου 2018 21 ώρες πριν, paparovic είπε α) Αν αυτό προτιμάς, ναι, το έχω δει να το κάνουν έτσι. Προσωπικά ξεχωρίζω σε διαφορετικά αρχεία το "τι είναι το model Χ του sequelize" και το "τι κάνω με τα models του sequelize". Οπότε εγώ κάνω require στον controller τα δεύτερα. β) Copy/paste από το πώς το στήνω εγώ και μετά σχόλια για το πώς θα το κάνεις εσύ: // `models/index.js` const Sequelize = require('sequelize'); const FS = require('fs'); const Path = require('path'); const config = require('../config'); const sequelize = new Sequelize( config.postgres.db, config.postgres.user, config.postgres.password, { host: config.postgres.host, dialect: 'postgres', logging: false, operatorsAliases: false, pool: { max: 5, idle: 30000, acquire: 60000, }, define: { charset: 'utf8', collate: 'utf8_general_ci', timestamps: true }, sync: { alter: true }, }, ); const db = {}; FS.readdirSync(__dirname) .filter(function (file) { return (file.indexOf(".") !== 0) && (file !== "index.js"); }) .forEach(function (file) { const model = sequelize.import(Path.join(__dirname, file)); db[model.name] = model; }); Object.keys(db).forEach(function (modelName) { if ("associate" in db[modelName]) { db[modelName].associate(db); } }); db.sequelize = sequelize; db.Sequelize = Sequelize; module.exports = db; // `models/patata.js` module.exports = function (sequelize, DataTypes) { const Patata = sequelize.define("Patata", { type: DataTypes.STRING, isActive: DataTypes.BOOLEAN, }, { tableName: 'Patata', timestamps: true, paranoid: true, updatedAt: true, }); Patata.associate = function (models) { Test.belongsTo(models.Tsanta, {foreignKey: 'tsantaId'}); }; return Patata; }; // `app.js` // ... const models = require('./models'); // ... const run = async () => { await models.sequelize[config.postgres.sync ? 'sync' : 'authenticate'](); app.use(require('./controllers')); server.listen(config.app.port, () => { logger.info('%s: listening on %s [%sms]', config.app.name, config.app.port, Date.now() - start); }); }; run(); Σε ένα directory όλα το models. Το index διαβάζει το κάθε file και το κάνει `import`. Το export του index είναι ένα wrapper object που έχει το instance και το class. Στο app κάνει require το models (index) και πριν ξεκινήσει τον express server περιμένει το instance του sequelize να κάνει την δουλειά του. Ok, για αυτό που θες εσύ, αντί να κάνεις export functions και objects όπως εγώ, κάνε τα wrap σε classes και προσάρμοσε αντίστοιχα τα require σου (και το `forEach` στο `readdirSync`, που υποθέτω θα το μετακινήσεις στον constructor). γ) Το `sync()` "φτιάχνει" τα tables στην βάση, ανάλογα με τα models σου. Έχεις εσύ 4 models και μια άδεια βάση. Κάνεις instanciate το sequelize με τα models και μόνο του πάει και κάνει την δουλειά του (αν έχεις βάλει το sync true στο config). Δεν κάνει crud, απλώς σου ετοιμάζει την βάση για να κάνεις το crud εσύ. α) Με το "τι είναι το model Χ του sequelize" εννοείς τα `Sequelize.define()`, σωστά; Έχω δει να προτείνουν στο StackOverflow αυτόν τον διαχωρισμό που είπες. Κάτι τελευταίο για το (α) ερώτημα. Όλα μου τα CRUD operations τα έχω υλοποιήσει σε ένα base model (ή σε κάποιο object άλλης κλάσης που δημιουργεί instance της το base model), και έτσι όλα τα models έχουν πρόσβαση σε αυτά κληρονομώντας το base model. Έτσι δεν πέφτω σε DRY (τουλάχιστον έτσι νομίζω) και δίνω και τα ονόματα που θέλω σε μεθόδους για CRUD στο κάθε model, κάπως έτσι: Spoiler // Method of a model which manages products createNewProduct(jsonOptions) { let promise = this.db.create(this.model, jsonOptions); return promise; } // Method of... let's say the Base Model create(model, jsonOptions) { // Call Sequelize's create() let promise = model.create(jsonOptions); return promise; } Πιστεύεις ότι είναι καλή πρακτική - το έχεις δει να το κάνουν κάπως έτσι; β) Σε υπερευχαριστώ! Τα μελετάω και θα επιστρέψω αφού ξεκαθαρίσω τα (α) και (γ). γ) Κατάλαβα. "Syncs all defined models to the DB." Οπότε αν έχω ήδη έτοιμο το schema της βάσης με την οποία συνδέομαι, δεν χρειάζομαι καθόλου το `sync()` και μπορώ να το παραλείψω, σωστά; Έστω όμως ότι το έχω, η επιλογή `force: true` βλέπω ότι κάνει DROP τους πίνακες που έχω ορίσει εάν αυτοί ήδη υπάρχουν στη βάση δεδομένων και δημιουργεί καινούργιους σύμφωνα με τους κανόνες που έχω βάλει στο `Sequelize.define()`, ενώ αν δεν υπάρχουν καθόλου απλά τους δημιουργεί. Το `alter: true` που χρησιμοποιείς τι ακριβώς κάνει; Είναι η default συμπεριφορά; Δεν το βρήκα στο API (latest version 4.38.0). Επίσης, αν κατάλαβα καλά, αφού κάνω instantiate το Sequelize με το sync ως ρύθμιση, τότε αυτό θα τρέξει οποτεδήποτε καλέσω το `Sequelize.define()`, σωστά; Δηλαδή δεν έχει σημασία που προηγείται του `define()`. Κάτι τελευταίο για το (γ) ερώτημα. Βλέπω ότι υπάρχουν δύο τρόποι για να καλέσεις το `sync()`. Είτε να το βάλεις στις ρυθμίσεις όπως μου έδειξες, είτε να το καλέσεις μετά: Spoiler Sequelize.sync({alter: true}) .then(() => { console.log("..."); }) .catch((error) => { console.log("This error occured", error); }); Υπάρχει κάποια σημαντική διαφορά ανάμεσα στους δύο τρόπους, εκτός του ότι με τον δεύτερο έχω κάποιον έλεγχο στη διάγνωση του promise λογκάροντας το resolve/reject;
paparovic Δημοσ. 14 Ιουλίου 2018 Δημοσ. 14 Ιουλίου 2018 (επεξεργασμένο) α) Ναι και ναι β) Cheers γ) Σωστά, το sync είναι για να κάνει εύκολη την ζωή μας στο development. https://github.com/sequelize/sequelize/issues/7728 Δεν καλείς τα define, τα define είναι μέσα σε ένα function το οποίο χρησιμοποιείται από το import. Όχι, απλά στο production θα απενεργοποιήσεις το sync, οπότε το προτιμώ στο config. Επεξ/σία 14 Ιουλίου 2018 από paparovic 1
precursor Δημοσ. 16 Ιουλίου 2018 Μέλος Δημοσ. 16 Ιουλίου 2018 Στις 14/7/2018 στις 11:42 ΠΜ, paparovic είπε γ) Σωστά, το sync είναι για να κάνει εύκολη την ζωή μας στο development. https://github.com/sequelize/sequelize/issues/7728 Δεν καλείς τα define, τα define είναι μέσα σε ένα function το οποίο χρησιμοποιείται από το import. Όχι, απλά στο production θα απενεργοποιήσεις το sync, οπότε το προτιμώ στο config. γ) Οπότε, εάν δεν χρησιμοποιήσω καθόλου το `sync()` έχει νόημα το να δώσω στο `define()` τα options/mappings για τα columns του πίνακα ή αρκεί να δώσω απλά το όνομά του, δηλαδή κάπως έτσι: const product = sequelize.define('products', {}) Και αν δώσω τα options για τα columns του πίνακα (type, value, allowNull κ.λπ.), με απενεργοποιημένο το `sync()`, θα επηρεαστούν καθόλου τα CRUD operations;;; Ξέρω ότι αν προσπαθήσω να γράψω κάτι στη βάση που δεν ταιριάζει θα λάβω ένα σφάλμα αφού δεν θα πετύχει η εγγραφή. Αν επιπλέον έχω options για τα columns θα λάβω και κάποιο σφάλμα από το Sequelize;;; Στην περίπτωση τώρα που χρησιμοποιώ το `sync()`, γνωρίζεις ποια/ες είναι η default συμπεριφορά του εάν δεν του δώσω καθόλου options; Αλλάζει το table structure από default; Υποτίθεται ότι το `alter: true` το κάνει αυτό: "Alters tables to fit models. Not recommended for production use. Deletes data in columns that were removed or had their type changed in the model." και απ' ό,τι είδα στα docs η default συμπεριφορά είναι `alter: false`, άρα το `sync()` δίχως καθόλου options τι κάνει; Υποθέτω ότι απλά κάνει create πίνακες, ίσως χωρίς columns αν δεν έχω δώσει column options, σωστά; Μέχρι στιγμής το χρησιμοποιούσα χωρίς options, με τον τρόπο από κάτω. Με τον δικό σου τρόπο στο config, υποθέτω ότι θα έβαζα απλά ένα `sync: {}` αν και μου φαίνεται το ίδιο με το να μην το έβαζα καθόλου. Sequelize.sync().then(...).catch(...); P.S.: Απ' ό,τι κατάλαβα από τη συζήτηση στο Github το alter έγινε σχετικά πρόσφατα stable.
paparovic Δημοσ. 16 Ιουλίου 2018 Δημοσ. 16 Ιουλίου 2018 (επεξεργασμένο) Define πρέπει να κάνεις ούτως ή άλλως διότι αλλιώς δεν θα μπορείς να κάνεις τίποτε χρήσιμο με το sequelize Τα defines και τα tables πρέπει να είναι perfectly matched, γι' αυτό βάζεις το sync όσο κάνεις development. Στο production δεν χρειάζεται γιατί δεν θα αλλάξουν ούτε τα defines ούτε τα tables. Όταν χρειαστεί να αλλάξεις το production, θα χρησιμοποιήσεις migrations. Χωρίς το alter, το sync φτιάχνει tables που δεν υπάρχουν. Αν το table υπάρχει, δεν το πειράζει. Επεξ/σία 16 Ιουλίου 2018 από paparovic 1
precursor Δημοσ. 16 Ιουλίου 2018 Μέλος Δημοσ. 16 Ιουλίου 2018 (επεξεργασμένο) 10 ώρες πριν, paparovic είπε Define πρέπει να κάνεις ούτως ή άλλως διότι αλλιώς δεν θα μπορείς να κάνεις τίποτε χρήσιμο με το sequelize Τα defines και τα tables πρέπει να είναι perfectly matched, γι' αυτό βάζεις το sync όσο κάνεις development. Στο production δεν χρειάζεται γιατί δεν θα αλλάξουν ούτε τα defines ούτε τα tables. Όταν χρειαστεί να αλλάξεις το production, θα χρησιμοποιήσεις migrations. Χωρίς το alter, το sync φτιάχνει tables που δεν υπάρχουν. Αν το table υπάρχει, δεν το πειράζει. Ωραία, ξεκαθάρισα αρκετά! Μου έχει μείνει μία τελευταία απορία πάνω σε αυτό το κομμάτι, σχετικά με το δεύτερο argument του `define()`. Ας πούμε λοιπόν ότι δεν χρησιμοποιούμε καθόλου το `sync()` και ότι έχουμε έτοιμο το schema της βάσης δεδομένων. Τα defines είναι απαραίτητα όπως και να 'χει. Ωστόσο, τα options/mappings που περνάμε σε αυτά είναι και εκείνα απαραίτητα; Έχω δει να δίνουν μόνο το όνομα των πινάκων στα defines, με κενά options. Κάπως έτσι: const product = sequelize.define('products', {}) Και εάν παρ' όλα αυτά δώσω τα options/mappings, έχει σημασία αν τα defines και τα tables είναι perfectly matched από τη στιγμή που δεν χρησιμοποιώ το `sync()` ; Επηρεάζουν δηλαδή κάπως τα CRUD operations με το να υπάρχουν; Επεξ/σία 16 Ιουλίου 2018 από precursor
paparovic Δημοσ. 16 Ιουλίου 2018 Δημοσ. 16 Ιουλίου 2018 47 λεπτά πριν, precursor είπε Ωραία, ξεκαθάρισα αρκετά! Μου έχει μείνει μία τελευταία απορία πάνω σε αυτό το κομμάτι, σχετικά με το δεύτερο argument του `define()`. Ας πούμε λοιπόν ότι δεν χρησιμοποιούμε καθόλου το `sync()` και ότι έχουμε έτοιμο το schema της βάσης δεδομένων. Τα defines είναι απαραίτητα όπως και να 'χει. Ωστόσο, τα options/mappings που περνάμε σε αυτά είναι και εκείνα απαραίτητα; Έχω δει να δίνουν μόνο το όνομα των πινάκων στα defines, με κενά options. Κάπως έτσι: const product = sequelize.define('products', {}) Και εάν παρ' όλα αυτά δώσω τα options/mappings, έχει σημασία αν τα defines και τα tables είναι perfectly matched από τη στιγμή που δεν χρησιμοποιώ το `sync()` ; Επηρεάζουν δηλαδή κάπως τα CRUD operations με το να υπάρχουν; Το mapping πρέπει να είναι σωστό αλλιώς μπορείς να πέσεις σε κάποιο edge case και να έχεις errors στο άσχετο. Από την άλλη, τα options επηρεάζουν την συμπεριφορά under the hood του sequelize. Οπότε δες από τα docs τι options χρειάζεσαι (ή όχι) για τα tables σου, πιθανώς να μην χρειάζεσαι κανένα. 1
Προτεινόμενες αναρτήσεις
Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε
Πρέπει να είστε μέλος για να αφήσετε σχόλιο
Δημιουργία λογαριασμού
Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!
Δημιουργία νέου λογαριασμούΣύνδεση
Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.
Συνδεθείτε τώρα