Автор оригинала: Shailesh Shekhawat.
Вступление
В качестве разработчика программного обеспечения в какой-то момент или иной момент или иначе придется иметь дело с миграциями базы данных.
Поскольку программное обеспечение или приложения развиваются и улучшаются в течение времени, ваша база данных также должна. И мы должны убедиться, что данные остаются последовательными в приложении.
Существует ряд различных способов того, что схема может измениться из одной версии вашего приложения к следующему.
- Добавлен новый участник
- Участник удален
- Член переименован
- Тип участника изменен
- Представление члена изменилось
Так как вы обрабатываете все вышеперечисленные изменения?
через гипю
Есть две стратегии:
- Напишите сценарий, который позаботится о модернизации схемы, а также понижение его до предыдущих версий
- Обновите ваши документы Как они используются
Второй гораздо больше, зависит от кода и должен оставаться в вашей кодовой базе. Если код каким-то образом удален, то многие документы не обновляются.
Например, если произошло 3 версии документа, [1, 2 и 3], и мы удаляем код обновления из версии 1 до версии 2, любые документы, которые все еще существуют в качестве версии 1, не обновляются. Я лично вижу это как накладные для поддержания кода, и он становится негибким.
Поскольку эта статья о автоматизации миграций, я собираюсь показать вам, как вы можете написать простой скрипт, который позаботится о изменениях схемы, а также и тесты на единицу.
Участник был добавлен
Когда член был добавлен в схему, существующий документ не будет иметь информацию. Таким образом, вам нужно запросить все документы, где этот член не существует и их обновляется.
Давайте продолжим написание некоторых кода.
Доступно уже несколько модулей NPM, но я использовал библиотеку Узел миграция Отказ Я тоже пробовал других, но некоторые из них больше не поддерживаются, и я столкнулся с проблемами, созданными другими.
Предпосылки
- Узел миграция – абстрактные миграционные рамки для узла
- Монгодб – родной водитель MongoDB для Nodejs
- Моча – Функция тестирования
- Чай – Утверждение библиотеки для написания тестовых случаев
- Bluebird : Обещание библиотеки для обработки вызовов Async API
- mkdirp : Как
mkdir -pно в Node.js. - Римраф :
RM -RFдля узла
Состояние миграции
Состояние миграции является наиболее важным ключом для отслеживания вашей текущей миграции. Без этого мы не сможем отслеживать:
- Сколько миграций было сделано
- Какова была последняя миграция
- Какая текущая версия схемы Мы используем
И без государств нет никакого способа откатиться, модернизировать и наоборот в другом состоянии.
Создание миграции
Создать миграцию, выполнить Миграция Создать
По умолчанию файл в ./migrations/ будет создан со следующим контентом:
'use strict'
module.exports.up = function (next) {
next()
}
module.exports.down = function (next) {
next()
}Давайте возьмем пример Пользователь Схема, где у нас есть недвижимость Имя который включает в себя оба Первый и Последнее имя.
Теперь мы хотим изменить схему, чтобы иметь отдельный Последнее Имя свойства.
Так что для того, чтобы автоматизировать это, мы будем читать Имя Во время выполнения и извлечь фамилию и сохранить его как новое свойство.
Создайте миграцию с помощью этой команды:
$ migrate create add-last-name.js
Этот звонок создаст ./migrations/proftertimestamp в milliseconds} -add-last-name.js под миграция Папка в корневом каталоге.
Давайте напишем код для добавления фамилии в схему, а также для удаления его.
Миграция
Мы найдем всех пользователей, где фамилия Собственность не существует и создает новую недвижимость фамилия в этих документах.
'use strict'
const Bluebird = require('bluebird')
const mongodb = require('mongodb')
const MongoClient = mongodb.MongoClient
const url = 'mongodb://localhost/Sample'
Bluebird.promisifyAll(MongoClient)
module.exports.up = next => {
let mClient = null
return MongoClient.connect(url)
.then(client => {
mClient = client
return client.db();
})
.then(db => {
const User = db.collection('users')
return User
.find({ lastName: { $exists: false }})
.forEach(result => {
if (!result) return next('All docs have lastName')
if (result.name) {
const { name } = result
result.lastName = name.split(' ')[1]
result.firstName = name.split(' ')[0]
}
return db.collection('users').save(result)
})
})
.then(() => {
mClient.close()
return next()
})
.catch(err => next(err))
}Внешнее миграция
Точно так же давайте напишем функцию, где мы удалим фамилия :
module.exports.down = next => {
let mClient = null
return MongoClient
.connect(url)
.then(client => {
mClient = client
return client.db()
})
.then(db =>
db.collection('users').update(
{
lastName: { $exists: true }
},
{
$unset: { lastName: "" },
},
{ multi: true }
))
.then(() => {
mClient.close()
return next()
})
.catch(err => next(err))
}Бег миграции
Проверьте, как здесь выполняется миграции: Бег миграции Отказ
Написание пользовательского состояния хранения
По умолчанию мигрировать хранит состояние миграции, которые были запущены в файле ( .migrate ).
.migrate Файл будет содержать следующий код:
{
"lastRun": "{timestamp in milliseconds}-add-last-name.js",
"migrations": [
{
"title": "{timestamp in milliseconds}-add-last-name.js",
"timestamp": {timestamp in milliseconds}
}
]
}Но вы можете предоставить пользовательский механизм хранения, если вы хотите сделать что-то другое, как хранить их в вашей базе данных выбора.
Двигатель хранения имеет простой интерфейс Загрузить (FN) и Сохранить (Set, Fn) Отказ
До тех пор, пока что идет как Установить выходит то же самое на нагрузка Тогда ты хорош!
Давайте создадим файл DB-Migrate-Store.js в корневом каталоге проекта.
const mongodb = require('mongodb')
const MongoClient = mongodb.MongoClient
const Bluebird = require('bluebird')
Bluebird.promisifyAll(MongoClient)
class dbStore {
constructor () {
this.url = 'mongodb://localhost/Sample' . // Manage this accordingly to your environment
this.db = null
this.mClient = null
}
connect() {
return MongoClient.connect(this.url)
.then(client => {
this.mClient = client
return client.db()
})
}
load(fn) {
return this.connect()
.then(db => db.collection('migrations').find().toArray())
.then(data => {
if (!data.length) return fn(null, {})
const store = data[0]
// Check if does not have required properties
if (!Object
.prototype
.hasOwnProperty
.call(store, 'lastRun')
||
!Object
.prototype
.hasOwnProperty
.call(store, 'migrations'))
{
return fn(new Error('Invalid store file'))
}
return fn(null, store)
}).catch(fn)
}
save(set, fn) {
return this.connect()
.then(db => db.collection('migrations')
.update({},
{
$set: {
lastRun: set.lastRun,
},
$push: {
migrations: { $each: set.migrations },
},
},
{
upsert: true,
multi: true,
}
))
.then(result => fn(null, result))
.catch(fn)
}
}
module.exports = dbStoreЗагрузить (FN) В этой функции мы просто проверяем, если существующий загруженный документ «Существующий миграционный» Ластран недвижимость и миграции множество.
Сохранить (Set, Fn) Здесь Установить предоставляется библиотекой, и мы обновляем Ластран ценность и добавление миграции к существующему массиву.
Вам может быть интересно, где вышеуказанный файл DB-Migrate-Store.js используется. Мы создаем его, потому что мы хотим хранить состояние в базе данных, а не в репозитории кода.
Ниже Тестовые примеры, где вы можете увидеть его использование.
Автоматизация миграционного тестирования
Установите Mocha:
$ npm install -g mocha
Состав
Чтобы настроить основные тесты, создайте новую папку, называемую «тест» в корне в проекте, то в этой папке добавляют папку, называемую миграция .
Структура вашего файла/папки теперь должна выглядеть так:
├── package.json
├── app
│ ├── server.js
│ ├── models
│ │ └── user.js
│ └── routes
│ └── user.js
└── test
migrations
└── create-test.js
└── up-test.js
└── down-test.jsТест – создать миграцию
Цель: Он должен создать каталог миграций и файл.
$ мигрировать Создать добавку
Это подразумевает создание файла ./migrations/proftertimestamp в milliseconds} -add-last-name.js под миграции Папка в корневом каталоге.
Теперь добавьте следующий код для create-test.js файл:
const Bluebird = require('bluebird')
const { spawn } = require('child_process')
const mkdirp = require('mkdirp')
const rimraf = require('rimraf')
const path = require('path')
const fs = Bluebird.promisifyAll(require('fs'))
describe('[Migrations]', () => {
const run = (cmd, args = []) => {
const process = spawn(cmd, args)
let out = ""
return new Bluebird((resolve, reject) => {
process.stdout.on('data', data => {
out += data.toString('utf8')
})
process.stderr.on('data', data => {
out += data.toString('utf8')
})
process.on('error', err => {
reject(err)
})
process.on('close', code => {
resolve(out, code)
})
})
}
const TMP_DIR = path.join(__dirname, '..', '..', 'tmp')
const INIT = path.join(__dirname, '..', '..', 'node_modules/migrate/bin', 'migrate-init')
const init = run.bind(null, INIT)
const reset = () => {
rimraf.sync(TMP_DIR)
rimraf.sync(path.join(__dirname, '..', '..', '.migrate'))
}
beforeEach(reset)
afterEach(reset)
describe('init', () => {
beforeEach(mkdirp.bind(mkdirp, TMP_DIR))
it('should create a migrations directory', done => {
init()
.then(() => fs.accessSync(path.join(TMP_DIR, '..', 'migrations')))
.then(() => done())
.catch(done)
})
})
})В приведенном выше тесте мы используем Миграция-init Команда для создания каталога миграции и удаление его после каждого тестового корпуса, используя Римраф который является RM -RF в Unix.
Позже мы используем Fscaccesssync Функция для проверки миграции папка существует или нет.
Тестовое задание – Миграция
Цель: Это должно добавить фамилия в схему и хранить миграцию.
Добавьте следующий код в Up-test.js файл:
const chance = require('chance')()
const generateUser = () => ({
email: chance.email(),
name: `${chance.first()} ${chance.last()}`
})
const migratePath = path.join(__dirname, '..', '..', 'node_modules/migrate/bin', 'migrate')
const migrate = run.bind(null, migratePath)
describe('[Migration: up]', () => {
before(done => {
MongoClient
.connect(url)
.then(client => {
db = client.db()
return db.collection('users').insert(generateUser())
})
.then(result => {
if (!result) throw new Error('Failed to insert')
return done()
}).catch(done)
})
it('should run up on specified migration', done => {
migrate(['up', 'mention here the file name we created above', '--store=./db-migrate-store.js'])
.then(() => {
const promises = []
promises.push(
db.collection('users').find().toArray()
)
Bluebird.all(promises)
.then(([users]) => {
users.forEach(elem => {
expect(elem).to.have.property('lastName')
})
done()
})
}).catch(done)
})
after(done => {
rimraf.sync(path.join(__dirname, '..', '..', '.migrate'))
db.collection('users').deleteMany()
.then(() => {
rimraf.sync(path.join(__dirname, '..', '..', '.migrate'))
return done()
}).catch(done)
})
})Точно так же вы можете записать миграцию и до () и после () Функции остаются в основном одинаковыми.
Заключение
Надеюсь, вы теперь можете автоматизировать свои изменения схемы с правильным тестированием.:)
Возьмите последний код из Репозиторий Отказ
Не стесняйтесь хлопать, если вы считаете это стоящим чтением!
Оригинал: “https://www.freecodecamp.org/news/how-to-automate-database-migrations-in-mongodb-d6b68efe084e/”