Представляем badcube.js - крошечную базу данных Node.js, о которой никто не просил

Никто не должен воспринимать это всерьез.

Репозиторий GitHub для этого проекта

Разговор с коллегой подсказал мне кое-что о программировании, о котором я не задумывался, пока он это не сказал: каждый файл - это просто набор строк. Вы должны пропустить его через лексер и синтаксический анализатор, чтобы заставить его делать что-нибудь, но в конечном итоге это всего лишь строка. В свете этого я начал думать о том, как база данных sqlite работает с локальными файлами в качестве базы данных. Почему нет эквивалента для noSQL? Как оказалось, их несколько, и один из моих любимых - TinyDB, написанный на Python. Когда я начал теряться в поисках Google, мне в голову пришла мысль, от которой я никак не мог избавиться: почему я не могу создать базу данных файловой системы с помощью Node.js? То, что началось как случайная мысль в пятницу вечером, превратилось в проект выходного дня, в результате которого был создан badcube. Я готов поспорить, что кто-то уже сделал нечто подобное, но для меня это отличный шанс попрактиковаться в работе с модулем файловой системы в Node.

Я должен был подумать, какие входы и выходы в базе данных, прежде чем что-либо писать. Естественно, как веб-разработчик, я склонен тяготеть к файлам JSON, так что я поступил именно так. Я также хотел иметь возможность брать выходные файлы из базы данных и легко импортировать их в любую базу данных на основе документов. В конечном итоге я остановился на массивах JSON, чтобы с файлами было легко работать, и они обладали всеми вышеупомянутыми функциями.

Но где же хранить эти файлы .json? Как поклонник MongoDB, я хотел бы иметь что-то аналогичное коллекциям ... тогда это папка с именем collectionsit. Благодаря этому я могу хранить каждую из аналогичных коллекций в собственном автономном файле .json внутри папки и импортировать каждую из них как объекты в память при запуске кода.

Первые шаги в программировании были простыми: читать в файлах. За несколько минут я изучил основы программы чтения коллекций, показанной ниже.

Оглядываясь назад, когда я пишу эту статью, начинать с асинхронной версии readdir было не самой блестящей идеей, но это было, по крайней мере, началом. Когда .forEach завершился, я смог отключить глобальный объект collections и увидеть все импортированные файлы. Написание этого научило меня двум действительно интересным вещам о require, которых я раньше не знал об этой функции. Во-первых, require является синхронным, что имеет смысл, учитывая, что вы не хотите начинать работу с модулями, которые еще не добавили в файл. Во-вторых, что более интересно, требуемый файл не должен иметь module.exportsили exports в нем, если вы передаете объект JSON или массив JSON. Это привело меня к необходимости немного глубже изучить, как это на самом деле реализовано в языке, и к отличной статье Фреда К. Шотта.

Это было довольно просто, и я почти совершил ошибку, остановившись здесь. Теперь, когда коллекции были загружены в объект collections, я понял, что могу просто взаимодействовать с каждой коллекцией так же, как и с любым объектом JavaScript. Это прекрасно, пока мне не придется писать повторяющийся код для базовых операций CRUD. Любой, кто использовал Mongoose или Sequelize, может сказать вам, что работать с моделью - с одинаковыми функциями для каждой коллекции (таблица для всех поклонников SQL) - значительно проще. Я должен встроить ORM-подобную функциональность прямо в базу данных! Теперь все снова интересно.

На этом этапе я подсознательно начал брать на себя обязательство, что это больше не тот проект, над которым я должен работать, а потом откладывать в долгий ящик. Я собираюсь сделать законный Пакет NPM! Ой! Я также могу сделать это отличным способом облегчить моим ученикам концепции баз данных с открытым исходным кодом, конструкторов, ORM и nosql с невероятно короткой настройкой.

Небольшое отступление: оказывается, что Git-LFS и npm adduser плохо работают. Если вы знаете отличный способ решить эту проблему, оставьте комментарий ниже!

Чтобы каждая коллекция могла иметь доступ к одним и тем же функциям, мне нужно предоставить им один и тот же прототип. Для этого мне нужно создать функцию-конструктор для Model, которая создает экземпляр нового объекта. Сам конструктор должен содержать информацию о коллекции, местонахождении ее файла (чтобы мы могли читать / писать в него позже) и имя самой модели.

Когда создается экземпляр этого конструктора, я также хочу присоединить его к глобальному объекту. Я переписал readdir функцию (обратите внимание на изменение readdirSync, чтобы сохранить рассудок), чтобы сделать это.

Обратите внимание также на изменение пути к переменной tmp, которая представляет коллекцию, когда она переносится в память из файла. Расположение каталога является результатом того, что этот файл стал обязательным модулем npm.

Наконец, мы можем перейти к отдельным функциям для операций CRUD в модели. Во-первых, find function. Если я передаю объект в эту функцию, я хочу найти первый объект в массиве, который представляет коллекцию, которая соответствует свойству. Поскольку это в настоящее время написано, оно работает только с одним свойством, но я надеюсь расширить его в более поздних версиях. Это будет верно для большинства функций, которые я здесь перечисляю. Эта функция была написана первой специально, потому что я могу повторно использовать ее как для функций обновления, так и для удаления. Метод ES6 Array.find упростил написание этой функции.

Точно так же мы можем сделать то же самое, используя Array.filter, чтобы создать метод findAll:

Я счастлив, что эта функция при передаче пустого объекта ведет себя аналогично функции Mongoose / MongoDB findAll при передаче пустого объекта.

Здесь все становится интересно. Первоначально я написал insert, а затем insertMany функции с вызовом fs.writeFileSync() в них. Приготовившись двигаться дальше, я понял, что все функции, которые создают, обновляют или удаляют данные, должны будут переписать файл. Чтобы СУШИТЬ код, прежде чем заходить слишком далеко, я решил абстрагироваться от перезаписи его собственным методом, показанным ниже.

Эта функция в том виде, в котором она написана сейчас, меня пугает, потому что я точно знаю, что она убьет производительность при операциях чтения / записи. Скорее всего, мне придется вернуться за рефакторингом для этого позже.

Функцию insert написать не так уж сложно, учитывая, что мы просто добавляем еще один элемент в массив. После помещения объекта в массив мы просто перезаписываем файл коллекции и возвращаем вставленный объект.

Впоследствии писать insertMany становится глупо просто после написания insert - this.insert ALL THE THINGS!

Для последних двух операций CRUD я написал код, который работал, но не обязательно работал хорошо:

Хотя splice позволяет просто модифицировать образец массива, особенно с использованием this.find, я должен был заметить, что что-то упустил при обновлении. Несмотря на то, что он выполняется, обновление в настоящее время перезаписывает объект на месте, а не обновляет определенные значения. На следующем проходе мне нужно будет переписать функцию, чтобы включить в нее свойства, которые уже есть в объекте, и изменить значение тех, которые передаются в качестве аргумента. Здесь мы также видим возврат метода this.rewrite(), который сохранил код невероятно чистым.

В общем, это была забавная кроличья нора, в которую можно было окунуться на выходных, и я счастлив, как это обернулось. Я с нетерпением жду возможности использовать этот код в качестве примера для обучения моих студентов и надеюсь, что некоторые из них проявят инициативу и внесут свой вклад в исправление некоторых ошибок, которые я оставил позади!

Я думаю, что этот проект будет хорош для демонстрации примеров того, как конструкторы могут использоваться для создания функциональных возможностей объектов, и я рад реализовать схемы, чтобы я мог также обсудить цепочку прототипов. Даже в полуфункциональном состоянии badcube может быть отличным примером того, как создавать объекты JS, которые представляют данные в «базе данных», в данном случае файлы .json в папке коллекций.

Что касается вариантов использования, я считаю, что badcube может найти применение в приложениях быстрого ввода данных, в которых настройка MongoDB представляет собой слишком много работы или где данные необходимо ввести в файл JSON и в конечном итоге импортировать в базу данных noSQL. В таком приложении тот факт, что библиотека выводит массив JSON, упростит импорт данных. Учитывая, что у badcube нет зависимостей (серьезно, нулевых зависимостей), его можно добавить в проект почти бесплатно с точки зрения занимаемого места.

Двигаясь вперед, я хотел бы вернуться и написать документацию для базы данных, а также переписать некоторые функции, обсуждаемые в этой статье. А пока всем весело, и пиар приветствуется!