«Монго, Санта-Мария!»
Немного исторического контекста
Базы данных делятся на несколько категорий. Самые ранние базы данных, вплоть до середины 1980-х годов, были так называемыми базами данных CODASYL. Эти данные организованы в записи и связаны друг с другом экземплярами разных типов записей с использованием хешей для формирования сети. Хотя в целом эффективные базы данных в стиле CODASYL, такие как IDMS от Cullinet Software и IMS от IBM, контекст и навигация требовали, чтобы приложение отображало физическую взаимосвязь между записями.
Когда появились новые требования к приложениям по объединению данных новыми способами, от администратора базы данных потребовалось создать новую физическую взаимосвязь для поддержки этого требования, и все это до того, как можно было начать разработку приложения. Это могло быть так же просто, как простое определение новых отношений, но это всегда требовало тщательного планирования и часто приводило к обширной реорганизации базы данных.
Предоставление такого уровня физической структуры базы данных приложениям привело к увеличению их сложности. Кроме того, встроенная зависимость физической схемы базы данных в приложении означала, что изменения базы данных потребовали обширной модификации и тестирования приложения.
Например, общие отношения - это отношения родительского и дочернего элементов, когда вхождение родительской записи владеет одним или несколькими экземплярами дочерних записей. Этот тип отношений используется для ведения записей сберегательного счета для физического лица и их связи с отдельными записями транзакций, представляющими действия против него.
Следующий псевдокод показывает, как приложение обращается к записям для этого типа отношений в базе данных CODASYL, в данном случае CA-IDMS:
INITIALIZE ACCOUNT Move account number ‘111111’ to the Account field in record OBTAIN CALC ACCOUNT Do-Forever OBTAIN NEXT Transaction WITHIN Account-Transaction If no more Transaction records then BREAK If transaction-type = ‘deposit’ then <<do something with the data>> End-If End-Do
В начале 1980-х годов начал распространяться новый тип баз данных, основанный на языке структурированных запросов (SQL). Основное преимущество базы данных SQL перед базой данных CODASYL состоит в том, что манипулировать данными было проще, поскольку данные в базе данных SQL организованы реляционно, а не физически. Логика приложения была основана на концепции, согласно которой данные будут возвращаться в табличном формате, состоящем из строк и столбцов, и данные из разных таблиц могут быть агрегированы на основе общих значений данных в таблицах, а не на физических связях. Это означало, что прикладные программы стало проще писать и поддерживать.
SELECT account-no, transaction-date, transaction-type, transaction-amount FROM ACCOUNT, TRANSACTION WHERE account-no = ‘111111’ AND transaction-account-no = account-no AND transaction-type = ‘deposit’ END-SELECT
Приведенный выше пример SQL демонстрирует мощь SQL. Вместо того, чтобы требовать навигационной логики в приложении для обхода базы данных, данные извлекаются на основе значения общего ключа между учетной записью и транзакцией. А именно номер счета. Директива SELECT приводит к тому, что система управления базами данных возвращает точный набор данных, необходимых для программы.
С появлением Интернета в конце 1990-х годов приложения перешли от работы со строго структурированными данными, такими как счета и транзакции, к работе с неструктурированными данными, такими как простой текст. Не менее важным было то, что приложениям необходимо было иметь возможность быстро адаптироваться к новым требованиям и масштабироваться для поддержки объемов транзакций, которые намного превышали то, что было в старых мэйнфреймах и средах распределенных вычислений.
База данных NoSQL была разработана для обеспечения среды, которая поддерживает изменения без необходимости радикальной реинжиниринга базовой модели данных, больших объемов данных и архитектуры, которая легко масштабируется.
Что такое MongoDB?
MongoDB - это система управления базами данных NoSQL с открытым исходным кодом, которая содержит следующие функции:
- Специальные запросы
- Индексирование
- Балансировка нагрузки
- Реплицированное файловое хранилище на нескольких серверах
- Агрегация данных
- Выполнение Javascript на стороне сервера
- Закрытые коллекции
MongoDB - это * основанная на документах * система управления базами данных, которая использует формат хранения в стиле JSON, известный как двоичный JSON или BSON, для достижения высокой пропускной способности. BSON позволяет приложениям легко извлекать данные и манипулировать ими, а также позволяет эффективно индексировать, отображать и вкладывать свойства для поддержки сложных операций и выражений запросов.
Например, используя приведенный выше пример учетной записи, все транзакции для учетной записи «111111», которые являются депозитами, могут быть получены с помощью:
db.transaction.find({ transaction-account-no:/’111111', transaction-type: /’deposit’/ })
Структура приложения MongoDB
Приложения MongoDB состоят из трех основных компонентов:
- Установите соединение с экземпляром MongoDB
- Логика для доступа и управления данными базы данных
- Закройте соединение с экземпляром MongoDB.
Установите соединение с экземпляром MongoDB
Прежде чем какие-либо запросы могут быть отправлены в MongoDB, необходимо сначала создать соединение. Установление соединения аналогично телефонному звонку, поскольку вам нужен «номер телефона» экземпляра MongoDB и база данных, с которой должно взаимодействовать приложение. Этот «номер телефона» состоит из следующего:
- Имя хоста экземпляра MongoDB
- Имя базы данных
Помимо «номера телефона» также могут быть указаны следующие параметры подключения.
- Идентификатор пользователя и пароль
- Параметры
Имя хоста и имя базы данных являются обязательными, но остальные параметры подключения необязательны. Для простоты в следующих примерах используются только имена хоста и базы данных. Однако в производственной среде вам всегда нужно защищать доступ к базе данных, требуя, чтобы пользователи и приложения аутентифицировались, указав авторизованный идентификатор пользователя и пароль.
Поскольку доступ к базе данных обычно распределяется между несколькими исходными файлами в приложении, рекомендуется изолировать имена хоста и базы данных в config.js
файле, который является общим для всех компонентов приложения. Преимущество этого заключается в том, что изменение одного или обоих из них требует обновления только файла config.js
, а не каждого исходного файла приложения, для которого требуется соединение.
Содержимое файла config.js
:
let config = {}; config.db = {}; // Create properties on the config.db object for the host and // database names config.db.host = ‘localhost:27017’; config.db.name = ‘account’; module.exports = config;
Чтобы установить соединение MongoDB с использованием свойств в файле config.js
, необходимо выдать require
, чтобы можно было ссылаться на свойства в нем из исходного файла, создающего соединение. Эти свойства затем используются для создания строки подключения URI и ссылки MongoClient
.
через которые будут отправляться последующие запросы к базе данных. Первым таким запросом является mongoClient.connect(mongoUri)
, который создает соединение и возвращает ссылку на экземпляр клиента.
const config = require(‘../config’); const mongodb = require(‘mongodb’); ... // Establish a mongo connection using settings from the config.js // // file const mongoUri = `mongodb://${config.db.host}/${config.db.name}`; const mongoClient = mongodb.MongoClient; ... mongoClient.connect(mongoUri) .then((db) => { ... }) .catch((err) => { ... });
Логика для доступа и управления данными базы данных
Как только соединение установлено, приложение готово для доступа и управления документами в базе данных. Первым шагом в этом процессе является определение коллекции документов в базе данных. Концептуально это похоже на таблицу в реляционной базе данных (RDBMS). В отличие от таблиц в СУБД, все строки которых имеют один и тот же формат, коллекция может содержать разные типы документов, каждый из которых имеет свой формат.
В нашем примере коллекция создается вызовом функции:
collection = accountsDb.collection(‘accounts’);
который определяет ссылку на коллекцию accounts
.
После создания коллекции ее можно использовать для вызова функций MongoDB для доступа к документам в базе данных. Например, collection.count()
возвращает количество документов в коллекции.
... mongoClient.connect(mongoUri) .then((db) => { accountsDb = db; collection = accountsDb.collection(‘accounts’); log.addEntry(‘Successfully connected to MongoDB’); return collection.count(); }) .then((count) => { log.addEntry(`Existing record count: ${count}`); return collection.find(); }) .then((cursor) => { cursor.each((error, anAccount) => { if (anAccount === null) { log.writeLog(‘normal’, response, ‘Findall test successfully completed’); return; } log.addEntry(`Account: account_no:${anAccount.account_no} owner_fname:${anAccount.owner_fname} owner_mi:${anAccount.owner_mi} owner_lname:${anAccount.owner_lname}`); }); ... }) .catch((err) => { ... });
Важно отметить тот факт, что вместо использования обратных вызовов в наших вызовах функций MongoDB мы используем Javascript Promises. Это было сделано, чтобы избежать попадания в ад обратных вызовов, который может возникнуть в результате вложенных обратных вызовов. Основным атрибутом ада обратного вызова является то, что исходный код принимает пирамидальную форму, и его влияние становится все труднее понимать и поддерживать по мере увеличения количества уровней.
Обещания помогают устранить «гнездо», которое упрощает код, делая его более легким для подготовки и, следовательно, более легким в обслуживании. Это работает так: логика в .then()
выполняется только тогда, когда соответствующее обещание было разрешено. И наоборот, .catch()
выполняется, только если обещание было отклонено. Другими словами, если была обнаружена ошибка.
Закройте соединение с экземпляром MongoDB.
После того, как приложение достигнет своей цели и данные из MongoDB больше не будут извлекаться, рекомендуется корректно завершить соединение с экземпляром MongoDB. Это достигается закрытием соединения. В приведенном выше примере это делается путем вызова функции accountsDb.close()
, чтобы остановить соединение и освободить все ресурсы, выделенные для него.
... mongoClient.connect(mongoUri) .then((db) => { accountsDb = db; ... }) .then((count) => { ... }) .then((cursor) => { ... accountsDb.close(); }) .catch((err) => { ... });
Вы могли заметить, что в первой .then(db)
функции значение db
было сохранено в переменной с именем accountDb
. Это было сделано в ожидании того, что позже в логике приложения потребуется закрыть соединение. Это было необходимо, поскольку объем db
ограничен этой первой .then(db)
функцией.
Еще одно соображение: бывают случаи, когда вы не хотите закрывать соединение. Поскольку открытие и закрытие соединений относительно дороги, приложения большого объема могут захотеть использовать пул соединений для повторного использования соединений, а не постоянно открывать и закрывать новые.
Что такое мангуст?
MongooseJS - это Object Document Mapper (ODM), который упрощает использование MongoDB, переводя документы в базе данных MongoDB в объекты в программе. Помимо MongooseJS, для MongoDB было разработано несколько других ODM, включая Doctrine, MongoLink и Mandango.
Три основных преимущества использования Mongoose по сравнению с родным MongoDB:
- MongooseJS предоставляет слой абстракции поверх MongoDB, который устраняет необходимость использования именованных коллекций.
- Модели в Mongoose выполняют основную часть работы по установке значений по умолчанию для свойств документа и проверки данных.
- Функции могут быть прикреплены к моделям в MongooseJS. Это позволяет беспрепятственно включать новые функции.
- В запросах используется цепочка функций, а не встроенная мнемоника, что приводит к тому, что код становится более гибким и читаемым, а значит, и более удобным в обслуживании.
Конечным результатом этого является упрощение доступа к базе данных из приложений. Основным недостатком Mongoose является то, что абстракция достигается за счет производительности по сравнению с исходной MongoDB.
Концепции мангуста
Mongoose использует схемы для моделирования данных, которые приложение хочет хранить и обрабатывать в MongoDb. Сюда входят такие функции, как приведение типов, проверка, построение запросов и многое другое.
Схема описывает атрибуты свойств (также называемых полями), которыми будет управлять приложение. Эти атрибуты включают в себя такие вещи, как:
- Тип данных (например, строка, число и т. Д.).
- Независимо от того, является ли это обязательным или необязательным.
- Является ли значение уникальным, что означает, что базе данных разрешено содержать только один документ с таким значением в этом свойстве.
Модель создается из схемы и определяет документ, с которым будет работать приложение. Точнее, модель - это класс, который определяет документ со свойствами и поведением, объявленными в нашей схеме. Все операции с базой данных, выполняемые с документом с помощью Mongoose, должны ссылаться на модель.
Чем код Mongoose отличается от MongoDB?
Первое различие между Mongoose и родным приложением MongoDB заключается в том, что модуль, содержащий схему и модель, должен быть создан в каталоге models
.
Определение схемы довольно интересно и полезно, поскольку оно может указывать атрибуты каждого свойства, включая тип данных, независимо от того, является ли оно обязательным или необязательным при вставке или обновлении, а также является ли его значение уникальным или нет.
Лучше всего, чтобы этот файл имел то же имя, что и модель. Первый символ этого файла находится в верхнем регистре, поскольку модель - это класс, построенный на основе схемы. Поэтому, как и в любом классе, первым символом должна быть заглавная буква.
В нашем примере следующий файл Account.js
содержит определения схемы и модели Mongoose.
// Mongoose schema and model definitions const mongoose = require(‘mongoose’); const Schema = mongoose.Schema; // Create the schema for the Account database const accountSchema = new Schema({ account_no: { type: Number, required: true, unique: true }, owner_fname: { type: String, required: true, unique: false }, owner_mi: { type: String, required: true, unique: false }, owner_lname: { type: String, required: true, unique: false }, created_on: { type: Date, required: false, unique: false }, updated_on: { type: Date, required: false, unique: false }, }); // Create a model for the schema const Account = mongoose.model(‘Account’, accountSchema); module.exports = Account;
Второе важное отличие, которое, возможно, относится к каждому разработчику, заключается в том, что запросы легче создавать и читать в Mongoose, чем в native-MongoDb. Запросы MongoDB состоят из структурированного BSON, определяющего имена свойств документа, операторы и значения, которые вместе определяют способ фильтрации документов.
MongoDB предоставляет богатый набор операторов запросов, которые попадают в одну из следующих категорий высокого уровня:
- Операторы сравнения
- Логические операторы
- Операторы элемента
- Операторы оценки
- Геопространственные операторы
- Операторы массива
- Побитовые операторы
- Операторы проекции
Пример запроса MongoDB, который выбирает документы, в которых owner_fname
равно («$ eq») значению «Roger», показан ниже.
collection.find({ owner_fname: { $eq: ‘Roger’ } });
Сравните это с Mongoose, который использует комбинацию функций и цепочки функций, а не операторов для фильтрации документов. Хотя версия запроса Mongoose длиннее, чем ее эквивалент MongoDB, она понятнее для читателя и может оказаться короче, поскольку запросы становятся более сложными и в BSON требуется больше свойств.
const query = Account.find({}) .where(‘owner_fname’).equals(‘Roger’); query.exec();
Ниже показано построчное сравнение кода программы на базе нативной MongoDB и ее эквивалента Mongoose.
Заключение
Весь пример кода, использованный в этой статье, можно найти на GitHub.
Я надеюсь, что эта информация будет полезной, и я также с нетерпением жду любых вопросов и комментариев, которые могут у вас возникнуть. Хорошего дня и делайте великие дела!
Что такое Чингу?
Присоединяйтесь к нам на Chingu.io, чтобы выйти из Учебного чистилища, присоединившись к одной из наших удаленных проектных групп Voyage, чтобы создавать приложения, совершенствовать жесткие навыки и изучать новые мягкие навыки. Мы помогаем разработчикам преодолеть разрыв между тем, чему они научились, и навыками, которые ищут работодатели.