В этом посте мы рассмотрим несколько причин, по которым вам следует избегать использования ORM (реляционное картирование объекта) в вашем проекте. В то время как концепции, обсуждаемые в этом посте, применимы к каждому языку и платформе, примеры кода будут записаны в node.js-со вкусом JavaScript, и мы рассмотрим пакеты, полученные из репозитория NPM.
В первую очередь: я не собираюсь рассеивать ни один из модулей, упомянутых в этом посте. Много тяжелой работы пошло на каждого из них. Они используются производственными приложениями по всему миру, которые весело реагируют на множество запросов каждый день. Я также развернул приложения, используя ORM и ничего не сожалею.
Следуйте
Ормы являются мощными инструментами. ORM, которые мы будем изучать в этом посте, способны общаться с SQL Backends, такими как SQLite, PostgreSQL, MySQL и MSSQL. Примеры в этом посте будут использовать PostgreSQL, который является очень мощным SQL -сервером с открытым исходным кодом. Есть ORM, способные общаться с Backends NoSQL, такими как Mongoose Orm, поддерживаемый MongoDB, но мы не будем рассматривать тех, кто в этом посте.
Во -первых, запустите следующие команды, чтобы запустить экземпляр PostgreSQL локально. Он будет настроен таким образом, чтобы запрашиваться в порт PostgreSQL по умолчанию на LocalHost: 5432 будет перенаправлен в контейнер. Он также напишет файлы на диск в вашем домашнем каталоге, чтобы последующие экземпляры сохранили данные, которые мы уже создали.
mkdir -p ~/data/pg-node-orms docker run --name pg-node-orms -p 5432:5432 -e POSTGRES_PASSWORD=hunter12 -e POSTGRES_USER=orm-user -e POSTGRES_DB=orm-db -v ~/data/pg-node-orms:/var/lib/postgresql/data -d postgres
Теперь, когда у вас есть база данных, нам нужно добавить несколько таблиц и данных в базу данных. Это позволит нам запрашивать данные и получить лучшее понимание различных слоев абстракции. Запустите следующую команду, чтобы запустить интерактивную подсказку PostgreSQL:
docker run -it --rm --link pg-node-orms:postgres postgres psql -h postgres -U orm-user orm-db
При типе приглашения в пароль из предыдущего блока кода Hunter12. Теперь, когда вы подключены, скопируйте и вставьте следующие запросы в подсказку и нажмите Enter.
CREATE TYPE item_type AS ENUM ( 'meat', 'veg', 'spice', 'dairy', 'oil' ); CREATE TABLE item ( id SERIAL PRIMARY KEY, name VARCHAR(64) NOT NULL, type item_type ); CREATE INDEX ON item (type); INSERT INTO item VALUES (1, 'Chicken', 'meat'), (2, 'Garlic', 'veg'), (3, 'Ginger', 'veg'), (4, 'Garam Masala', 'spice'), (5, 'Turmeric', 'spice'), (6, 'Cumin', 'spice'), (7, 'Ground Chili', 'spice'), (8, 'Onion', 'veg'), (9, 'Coriander', 'spice'), (10, 'Tomato', 'veg'), (11, 'Cream', 'dairy'), (12, 'Paneer', 'dairy'), (13, 'Peas', 'veg'), (14, 'Ghee', 'oil'), (15, 'Cinnamon', 'spice'); CREATE TABLE dish ( id SERIAL PRIMARY KEY, name VARCHAR(64) NOT NULL, veg BOOLEAN NOT NULL ); CREATE INDEX ON dish (veg); INSERT INTO dish VALUES (1, 'Chicken Tikka Masala', false), (2, 'Matar Paneer', true); CREATE TABLE ingredient ( dish_id INTEGER NOT NULL REFERENCES dish (id), item_id INTEGER NOT NULL REFERENCES item (id), quantity FLOAT DEFAULT 1, unit VARCHAR(32) NOT NULL ); INSERT INTO ingredient VALUES (1, 1, 1, 'whole breast'), (1, 2, 1.5, 'tbsp'), (1, 3, 1, 'tbsp'), (1, 4, 2, 'tsp'), (1, 5, 1, 'tsp'), (1, 6, 1, 'tsp'), (1, 7, 1, 'tsp'), (1, 8, 1, 'whole'), (1, 9, 1, 'tsp'), (1, 10, 2, 'whole'), (1, 11, 1.25, 'cup'), (2, 2, 3, 'cloves'), (2, 3, 0.5, 'inch piece'), (2, 13, 1, 'cup'), (2, 6, 0.5, 'tsp'), (2, 5, 0.25, 'tsp'), (2, 7, 0.5, 'tsp'), (2, 4, 0.5, 'tsp'), (2, 11, 1, 'tbsp'), (2, 14, 2, 'tbsp'), (2, 10, 3, 'whole'), (2, 8, 1, 'whole'), (2, 15, 0.5, 'inch stick');
Теперь у вас есть населенная база данных. Вы можете ввести \ уйти, чтобы отключиться от клиента PSQL и получить контроль над вашим терминалом. Если вы когда -нибудь захотите запустить Raw SQL команды снова, вы сможете снова запустить ту же команду Docker Run.
Наконец, вам также необходимо создать файл с именем connection.json, содержащий следующую структуру JSON. Это будет использоваться приложениями узла позже для подключения к базе данных.
{
"host": "localhost",
"port": 5432,
"database": "orm-db",
"user": "orm-user",
"password": "hunter12"
}
Слои абстракции
Прежде чем погрузиться в слишком много кода, давайте проясним несколько разных слоев абстракции. Как и все в информатике, есть компромиссы, когда мы добавляем слои абстракции. С каждым добавленным уровнем абстракции мы пытаемся обменять снижение производительности с увеличением производительности разработчика (хотя это не всегда так).
Низкий уровень: Драйвер базы данных
Это в основном настолько низкоуровневое, насколько вы можете получить-за исключением вручную генерировать пакеты TCP и доставить их в базу данных. Драйвер базы данных будет обрабатывать подключение к базе данных (а иногда и объединение соединений). На этом уровне вы будете писать необработанные строки SQL и доставить их в базу данных и получить ответ из базы данных. В экосистеме Node.js на этом уровне работает много библиотек. Вот три популярных библиотеки:
- mysql : MySQL (13K Stars/330K Weekly Downlocs)
- pg : Postgresql (6K Stars/520K Weekly Downlocs)
- SQLite3 : SQLite (3K Stars/120K Weekly Downlocs)
Каждая из этих библиотек, по сути, работает одинаково: возьмите учетные данные базы данных, создайте экземпляр нового экземпляра базы данных, подключитесь к базе данных и отправляйте его запросами в форме строки и асинхронно обрабатывают результат.
Вот простой пример, использующий модуль PG, чтобы получить список ингредиентов, необходимых для приготовления куриного тикка масала:
#!/usr/bin/env node
// $ npm install pg
const { Client } = require('pg');
const connection = require('./connection.json');
const client = new Client(connection);
client.connect();
const query = `SELECT
ingredient.*, item.name AS item_name, item.type AS item_type
FROM
ingredient
LEFT JOIN
item ON item.id = ingredient.item_id
WHERE
ingredient.dish_id = $1`;
client
.query(query, [1])
.then(res => {
console.log('Ingredients:');
for (let row of res.rows) {
console.log(`${row.item_name}: ${row.quantity} ${row.unit}`);
}
client.end();
});
Средний уровень: Запрос
Это промежуточный уровень между использованием более простого модуля драйвера базы данных по сравнению с полноценным ORM. Наиболее заметным модулем, который работает на этом слое, является Knex. Этот модуль способен генерировать запросы для нескольких различных диалектов SQL. Этот модуль зависит от одной из вышеупомянутых библиотек – вам нужно установить конкретные, которые вы планируете использовать с помощью KNEX.
- Knex : Сборщик запросов (8K звезд/170 тыс. Еженедельные загрузки)
При создании экземпляра KNEX вы предоставляете детали подключения, а также диалект, на котором вы планируете использовать, и затем можете начать создавать запросы. Запросы, которые вы пишете, будут очень похожи на основные запросы SQL. Одна из них заключается в том, что вы можете программно генерировать динамические запросы гораздо более удобным способом, чем если бы вы объединяли строки вместе, чтобы сформировать SQL (который часто вводит уязвимости безопасности).
Вот простой пример, использующий модуль Knex, чтобы получить список ингредиентов, необходимых для приготовления куриного тикка масала:
#!/usr/bin/env node
// $ npm install pg knex
const knex = require('knex');
const connection = require('./connection.json');
const client = knex({
client: 'pg',
connection
});
client
.select([
'*',
client.ref('item.name').as('item_name'),
client.ref('item.type').as('item_type'),
])
.from('ingredient')
.leftJoin('item', 'item.id', 'ingredient.item_id')
.where('dish_id', '=', 1)
.debug()
.then(rows => {
console.log('Ingredients:');
for (let row of rows) {
console.log(`${row.item_name}: ${row.quantity} ${row.unit}`);
}
client.destroy();
});
Высокий уровень: ORM
Это самый высокий уровень абстракции, который мы собираемся рассмотреть. При работе с ORM нам обычно нужно делать гораздо больше конфигурации. Точка ORM, как следует из названия, состоит в том, чтобы отобразить запись в реляционной базе данных с объектом (обычно, но не всегда, экземпляр класса) в нашем приложении. Это означает, что мы определяем структуру этих объектов, а также их отношения в коде нашего приложения.
- Продолжение : (16K Stars/270K Weekly загрузки)
- Книжная полка : На основе Knex (5K Stars/23K Weekly загрузки)
- Waterline : (5K Stars/20K Weekly Downlocs)
- возражение : На основе Knex (3K Stars/20k Weekly загрузки)
В этом примере мы рассмотрим самых популярных из ORM, продолжение. Мы также собираемся смоделировать отношения, представленные в нашей оригинальной схеме PostgreSQL, с использованием Scentize. Вот тот же пример, используя Продолжение Модуль, чтобы получить список ингредиентов, необходимых для приготовления куриного тикка масала:
#!/usr/bin/env node
// $ npm install sequelize pg
const Sequelize = require('sequelize');
const connection = require('./connection.json');
const DISABLE_SEQUELIZE_DEFAULTS = {
timestamps: false,
freezeTableName: true,
};
const { DataTypes } = Sequelize;
const sequelize = new Sequelize({
database: connection.database,
username: connection.user,
host: connection.host,
port: connection.port,
password: connection.password,
dialect: 'postgres',
operatorsAliases: false
});
const Dish = sequelize.define('dish', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
name: { type: DataTypes.STRING },
veg: { type: DataTypes.BOOLEAN }
}, DISABLE_SEQUELIZE_DEFAULTS);
const Item = sequelize.define('item', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
name: { type: DataTypes.STRING },
type: { type: DataTypes.STRING }
}, DISABLE_SEQUELIZE_DEFAULTS);
const Ingredient = sequelize.define('ingredient', {
dish_id: { type: DataTypes.INTEGER, primaryKey: true },
item_id: { type: DataTypes.INTEGER, primaryKey: true },
quantity: { type: DataTypes.FLOAT },
unit: { type: DataTypes.STRING }
}, DISABLE_SEQUELIZE_DEFAULTS);
Item.belongsToMany(Dish, {
through: Ingredient, foreignKey: 'item_id'
});
Dish.belongsToMany(Item, {
through: Ingredient, foreignKey: 'dish_id'
});
Dish.findOne({where: {id: 1}, include: [{model: Item}]}).then(rows => {
console.log('Ingredients:');
for (let row of rows.items) {
console.log(
`${row.dataValues.name}: ${row.ingredient.dataValues.quantity} ` +
row.ingredient.dataValues.unit
);
}
sequelize.close();
});
Теперь, когда вы увидели пример того, как выполнять подобные запросы, используя различные слои абстракции, давайте погрузимся в причины, по которым вы должны опасаться использования ORM.
Причина 1: Вы узнаете неправильно
Многие люди собирают ORM, потому что они не хотят тратить время, чтобы выучить основной SQL (структурированный язык запросов). Вера часто заключается в том, что SQL трудно изучить, и что, изучая ORM, мы можем просто написать наши приложения, используя один язык вместо двух. На первый взгляд, это, кажется, выдерживает. ORM будет написан на том же языке, что и остальная часть приложения, в то время как SQL является совершенно другим синтаксисом.
Однако есть проблема с этой линией мышления. Проблема в том, что ORM представляют некоторые из самых сложных библиотек, которые вы можете получить в свои руки. Площадь поверхности ORM очень большая, и изучение ее внутри и снаружи – нелегкая задача.
После того, как вы узнали конкретный ORM, это знание, вероятно, не будет так хорошо перенести. Это верно, если вы переключитесь с одной платформы на другую, например, js/node.js на C#/. Net. Но, возможно, еще менее очевидно, что это верно, если вы переключитесь с одного ORM на другой на одной платформе, например, Scilezize to Booksefield с Node.js. Рассмотрим следующие примеры ORM, каждый из которых генерирует список всех рецептов, которые являются вегетарианскими:
Продолжение:
#!/usr/bin/env node
// $ npm install sequelize pg
const Sequelize = require('sequelize');
const { Op, DataTypes } = Sequelize;
const connection = require('./connection.json');
const DISABLE_SEQUELIZE_DEFAULTS = {
timestamps: false,
freezeTableName: true,
};
const sequelize = new Sequelize({
database: connection.database,
username: connection.user,
host: connection.host,
port: connection.port,
password: connection.password,
dialect: 'postgres',
operatorsAliases: false
});
const Item = sequelize.define('item', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
name: { type: DataTypes.STRING },
type: { type: DataTypes.STRING }
}, DISABLE_SEQUELIZE_DEFAULTS);
// SELECT "id", "name", "type" FROM "item" AS "item"
// WHERE "item"."type" = 'veg';
Item
.findAll({where: {type: 'veg'}})
.then(rows => {
console.log('Veggies:');
for (let row of rows) {
console.log(`${row.dataValues.id}t${row.dataValues.name}`);
}
sequelize.close();
});
Книжная полка:
#!/usr/bin/env node
// $ npm install bookshelf knex pg
const connection = require('./connection.json');
const knex = require('knex')({
client: 'pg',
connection,
// debug: true
});
const bookshelf = require('bookshelf')(knex);
const Item = bookshelf.Model.extend({
tableName: 'item'
});
// select "item".* from "item" where "type" = ?
Item
.where('type', 'veg')
.fetchAll()
.then(result => {
console.log('Veggies:');
for (let row of result.models) {
console.log(`${row.attributes.id}t${row.attributes.name}`);
}
knex.destroy();
});
ВАМЕРИНАЙН:
#!/usr/bin/env node
// $ npm install sails-postgresql waterline
const pgAdapter = require('sails-postgresql');
const Waterline = require('waterline');
const waterline = new Waterline();
const connection = require('./connection.json');
const itemCollection = Waterline.Collection.extend({
identity: 'item',
datastore: 'default',
primaryKey: 'id',
attributes: {
id: { type: 'number', autoMigrations: {autoIncrement: true} },
name: { type: 'string', required: true },
type: { type: 'string', required: true },
}
});
waterline.registerModel(itemCollection);
const config = {
adapters: {
'pg': pgAdapter
},
datastores: {
default: {
adapter: 'pg',
host: connection.host,
port: connection.port,
database: connection.database,
user: connection.user,
password: connection.password
}
}
};
waterline.initialize(config, (err, ontology) => {
const Item = ontology.collections.item;
// select "id", "name", "type" from "public"."item"
// where "type" = $1 limit 9007199254740991
Item
.find({ type: 'veg' })
.then(rows => {
console.log('Veggies:');
for (let row of rows) {
console.log(`${row.id}t${row.name}`);
}
Waterline.stop(waterline, () => {});
});
});
Возражение:
#!/usr/bin/env node
// $ npm install knex objection pg
const connection = require('./connection.json');
const knex = require('knex')({
client: 'pg',
connection,
// debug: true
});
const { Model } = require('objection');
Model.knex(knex);
class Item extends Model {
static get tableName() {
return 'item';
}
}
// select "item".* from "item" where "type" = ?
Item
.query()
.where('type', '=', 'veg')
.then(rows => {
for (let row of rows) {
console.log(`${row.id}t${row.name}`);
}
knex.destroy();
});
Синтаксис для простой операции чтения сильно варьируется между этими примерами. Поскольку операция, которую вы пытаетесь выполнить, увеличивает сложность, такую как операции, включающие несколько таблиц, синтаксис ORM будет варьироваться от реализаций еще больше.
Существует как минимум десятки ORM для Node.js, и, по крайней мере, сотни ORM для всех платформ. Изучение всех этих инструментов будет кошмаром!
К счастью для нас, на самом деле нужно беспокоиться только о нескольких диалектах SQL. Узнав, как генерировать запросы, используя RAW SQL, вы можете легко перенести эти знания между различными платформами.
Причина 2: сложные вызовы ORM могут быть неэффективными
Напомним, что цель ORM состоит в том, чтобы взять базовые данные, хранящиеся в базе данных, и составить их в объект, который мы можем взаимодействовать в нашем приложении. Это часто связано с некоторыми неэффективными, когда мы используем ORM для получения определенных данных.
Рассмотрим, например, запросы, которые мы впервые рассмотрели в разделе о слоях абстракции. В этом запросе мы просто хотели список ингредиентов и их количества для конкретного рецепта. Сначала мы сделали запрос, написав SQL вручную. Затем мы сделали запрос, используя строитель запросов, Knex. Наконец, мы сделали запрос, используя ORM, продолжение. Давайте посмотрим на запросы, которые были сгенерированы этими тремя командами:
Вручную написано драйвером “PG”:
Этот первый запрос – именно тот, который мы написали вручную. Он представляет собой самый краткий метод, чтобы получить именно те данные, которые мы хотим.
SELECT ingredient.*, item.name AS item_name, item.type AS item_type FROM ingredient LEFT JOIN item ON item.id = ingredient.item_id WHERE ingredient.dish_id = ?;
Когда мы предварительно префикс этот запрос с объяснением и отправляем его на сервер PostgreSQL, мы получаем операцию стоимости 34.12 Анкет
Сгенерировано с помощью строителя «Knex»:
Этот следующий запрос был в основном сгенерирован для нас, но из -за явного характера строителя запросов Knex мы должны иметь довольно хорошее ожидание того, как будет выглядеть выход.
select *, "item"."name" as "item_name", "item"."type" as "item_type" from "ingredient" left join "item" on "item"."id" = "ingredient"."item_id" where "dish_id" = ?;
Новые линии были добавлены мной для читаемости. Помимо некоторых незначительных форматирования и ненужных имен таблиц в моем рукописном примере, эти запросы идентичны. На самом деле, как только запрос объяснения запускается, мы получаем одинаковую оценку 34.12 Анкет
Сгенерировано с помощью “Sedizize” ORM:
Теперь давайте посмотрим на запрос, созданный ORM:
SELECT "dish"."id", "dish"."name", "dish"."veg", "items"."id" AS "items.id", "items"."name" AS "items.name", "items"."type" AS "items.type", "items->ingredient"."dish_id" AS "items.ingredient.dish_id", "items->ingredient"."item_id" AS "items.ingredient.item_id", "items->ingredient"."quantity" AS "items.ingredient.quantity", "items->ingredient"."unit" AS "items.ingredient.unit" FROM "dish" AS "dish" LEFT OUTER JOIN ( "ingredient" AS "items->ingredient" INNER JOIN "item" AS "items" ON "items"."id" = "items->ingredient"."item_id" ) ON "dish"."id" = "items->ingredient"."dish_id" WHERE "dish"."id" = ?;
Новые линии были добавлены мной для читаемости. Как вы можете сказать, этот запрос – Лот отличается от двух предыдущих запросов. Почему это ведет себя так по -разному? Что ж, из -за того, что мы определили отношения, продолжение пытается получить больше информации, чем то, о чем мы просили. В частности, мы получаем информацию о самом блюде, когда мы действительно заботимся о ингредиентах, принадлежащих этому блюду. Стоимость этого запроса, согласно объяснению, составляет 42.32 Анкет
Причина 3: Орм не может сделать все
Не все запросы могут быть представлены как операция ORM. Когда нам нужно генерировать эти запросы, мы должны вернуться к созданию запроса SQL вручную. Это часто означает, что кодовая база с тяжелым использованием ORM все еще будет иметь несколько рукописных запросов, разбросанных по этому поводу. Последствия здесь заключаются в том, что, как разработчик, работающий над одним из этих проектов, нам нужно знать как синтаксис ORM, так и некоторые базовые синтаксисы SQL.
Общая ситуация, которая не очень хорошо работает с ORM, – это когда запрос содержит подзадность. Подумайте о ситуации, когда я знаю, что я уже приобрел все ингредиенты для блюда № 2 в нашей базе данных, однако мне все еще нужно купить любые ингредиенты, необходимые для блюда № 1. Чтобы получить этот список, я мог бы запустить следующий запрос:
SELECT *
FROM item
WHERE
id NOT IN
(SELECT item_id FROM ingredient WHERE dish_id = 2)
AND id IN
(SELECT item_id FROM ingredient WHERE dish_id = 1);
Насколько мне известно, этот запрос не может быть чисто представлено с использованием вышеупомянутых ORM. Для борьбы с этими ситуациями, что ORM обычно предлагает возможность вводить необработанный SQL в интерфейс запроса.
Scentize предлагает метод .QUERY () для выполнения RAW SQL, как если бы вы использовали основной драйвер базы данных. С помощью книжной полки и возражений вы получаете доступ к объекту RAW KNEX, который вы предоставляете во время экземпляра, и можете использовать его для своих полномочий строителя запросов. Объект Knex также имеет. () Метод для выполнения RAW SQL. С помощью Scessize вы также получаете метод Scentize.literal (), который можно использовать для впрыскивания необработанного SQL в различных частях вызова ORM -последовательности. Но в каждой из этих ситуаций вам все равно нужно знать, что некоторые базовые SQL для создания определенных запросов.
Строители запросов: сладкое место
Использование модулей драйверов базы данных низкого уровня довольно заманчиво. При создании запроса для базы данных нет накладных расходов, поскольку мы пишем вручную. Общие зависимости, на которые опирается наш проект, также минимизируется. Тем не менее, генерирование динамических запросов может быть очень утомительным, и, на мой взгляд, является самым большим недостатком использования простого драйвера данных.
Рассмотрим, например, веб -интерфейс, где пользователь может выбрать критерии, когда он хочет получить элементы. Если есть только один вариант, который пользователь может ввести, например, цвет, наш запрос может выглядеть следующим образом:
SELECT * FROM things WHERE color = ?;
Этот единственный запрос прекрасно работает с простым драйвером базы данных. Тем не менее, рассмотрим, является ли цвет необязательным и есть второе необязательное поле, называемое is_heevy. Теперь нам нужно поддержать несколько различных перестановков этого запроса:
SELECT * FROM things; -- Neither SELECT * FROM things WHERE color = ?; -- Color only SELECT * FROM things WHERE is_heavy = ?; -- Is Heavy only SELECT * FROM things WHERE color = ? AND is_heavy = ?; -- Both
Тем не менее, по вышеупомянутым причинам, полностью изготовленный ORM не тот инструмент, который мы хотим достичь.
Построитель запросов в конечном итоге станет довольно хорошим инструментом в этих ситуациях. Интерфейс, выявленный KNEX, настолько близок к основному запросу SQL, что мы вынуждены всегда знать, как выглядит запрос SQL. Эти отношения похожи на то, как что -то вроде TypeScript переводится на JavaScript.
Использование сборки запроса является прекрасным решением, если вы полностью понимаете базовый SQL, который он генерирует. Никогда не используйте его как инструмент, чтобы скрыть от того, что происходит на нижнем слое. Используйте его только в качестве удобства и в ситуациях, когда вы точно знаете, что он делает. Если вы когда -нибудь подвергаетесь сомнению, какой сгенерированный запрос на самом деле выглядит так, как будто вы можете добавить поле отладки в экземплярный звонок Knex (). Это выглядит так:
const knex = require('knex')({
client: 'pg',
connection,
debug: true // Enable Query Debugging
});
Фактически, большинство библиотек, упомянутых в этом посте, включают в себя какой -то метод отладки выполняемых вызовов.
Мы рассмотрели три различных уровня взаимодействия с базой данных, а именно на драйверы базы данных низкого уровня, строители запросов и ORM высокого уровня. Мы также изучили компромиссы использования каждого уровня, а также генерируемые запросы SQL: это включает в себя сложность генерации динамических запросов с драйвером базы данных, дополнительной сложностью ORM и, наконец, сладкое место использования генератора запросов.
Спасибо за чтение и обязательно примите это во внимание, когда вы создаете следующий проект.
После того, как вы закончите следовать, вы можете запустить следующие команды, чтобы полностью удалить контейнер Docker и удалить файлы базы данных с вашего компьютера:
docker stop pg-node-orms docker rm pg-node-orms sudo rm -rf ~/data/pg-node-orms
Plug: Logrocket, DVR для веб -приложений
https://logrocket.com/signup/
Logrocket это инструмент регистрации фронта, который позволяет вам воспроизводить проблемы, как будто они произошли в вашем собственном браузере. Вместо того, чтобы догадаться, почему возникают ошибки, или просить пользователей экрана и журнала дамп, Logrocket позволяет воспроизвести сеанс, чтобы быстро понять, что пошло не так. Он отлично работает с любым приложением, независимо от фреймворта, и имеет плагины для регистрации дополнительного контекста из Redux, Vuex и @ngrx/Store.
В дополнение к журналам Redux и состоянию регистрации журналы консоли Logrocket записывают, ошибки JavaScript, StackTraces, сетевые запросы/ответы с заголовками + BODES, метаданные браузера и пользовательские журналы. Он также придает DOM записывать HTML и CSS на странице, воссоздавая видеопроблемные видео даже самых сложных одностраничных приложений.
Попробуйте бесплатно.
Пост Почему вы должны избегать ORM (с примерами в node.js) появился первым на Logrocke Blog Анкет
Оригинал: “https://dev.to/bnevilleoneill/why-you-should-avoid-orms-with-examples-in-node-js-3ib2”