«Монго, Санта-Мария!»

Немного исторического контекста

Базы данных делятся на несколько категорий. Самые ранние базы данных, вплоть до середины 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 состоят из трех основных компонентов:

  1. Установите соединение с экземпляром MongoDB
  2. Логика для доступа и управления данными базы данных
  3. Закройте соединение с экземпляром 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:

  1. MongooseJS предоставляет слой абстракции поверх MongoDB, который устраняет необходимость использования именованных коллекций.
  2. Модели в Mongoose выполняют основную часть работы по установке значений по умолчанию для свойств документа и проверки данных.
  3. Функции могут быть прикреплены к моделям в MongooseJS. Это позволяет беспрепятственно включать новые функции.
  4. В запросах используется цепочка функций, а не встроенная мнемоника, что приводит к тому, что код становится более гибким и читаемым, а значит, и более удобным в обслуживании.

Конечным результатом этого является упрощение доступа к базе данных из приложений. Основным недостатком 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, чтобы создавать приложения, совершенствовать жесткие навыки и изучать новые мягкие навыки. Мы помогаем разработчикам преодолеть разрыв между тем, чему они научились, и навыками, которые ищут работодатели.