Базы данных не новы. На самом деле они могут быть довольно скучными. Тем не менее, нам постоянно приходится повторять реализацию одних и тех же вещей снова и снова, потому что контекст, в котором мы создаем вещи, постоянно меняется. Но должно ли это иметь такое большое значение, работаем ли мы на стороне клиента или на стороне сервера, на PostgreSQL, MongoDB или Firebase, используя микросервисы, монолиты или функции Lambda? В конце концов, нам все равно придется решать одни и те же проблемы, хотя и в разных конфигурациях. Не знаю, как вы, но я очень устаю думать о миграции схем, REST API, живых миграциях данных, разрешении конфликтов для автономных приложений, синхронизируемых между несколькими устройствами, контроле доступа и обо всем этом мы продолжаем решать снова и снова на протяжении многих лет.

Так родился Storex. Memex (на GitHub), клиентская система полнотекстового поиска всего, что вы видели в Интернете, превратилась в многоцелевой инструмент управления знаниями, позволяющий делать заметки на страницах и в будущем совместно курировать знания. Мы стремимся создать его как можно более децентрализованным, но нам потребовалось гораздо больше времени для исследования доступных технологий, чтобы разработать хорошую архитектуру, отвечающую всем ожиданиям современного приложения. Поэтому нам нужна была свобода перемещать данные без привязки к определенной технологии или необходимости переписывать весь наш стек, как только выбор технологий станет более ясным.

Что такое Storex, а что нет

Storex - это набор пакетов, реализующих функциональные возможности всего, что вам нужно для создания полноценного приложения, обрабатывающего данные, которые являются достаточно модульными, чтобы их можно было повторно комбинировать любым необходимым способом. Базовый пакет предоставляет только способ описания вашей модели данных (у пользователя есть адреса электронной почты, которые могут иметь коды подтверждения и т. Д.), А также способ для различных бэкэндов выполнять операции с вашими данными. Как бэкенды затем реализуют эти операции, зависит от них. Кроме того, на его основе создаются различные пакеты, такие как пакет миграции схемы или будущий пакет управления доступом. Ядро Storex и связанные с ним пакеты основаны на одной философии: предоставить унифицированные способы обсуждения данных и взаимодействия с ними, а затем позволить различным серверным компонентам и пакетам реализовать эти вещи в разном контексте.

Из этого следует, что Storex - это не фреймворк, а библиотека: повторно используемые, очень слабо связанные пакеты, которые можно рекомбинировать. Он не включает реализацию шаблона ActiveRecord, хотя он может быть построен на его основе (чего я настоятельно не рекомендую, поскольку ActiveRecord поощряет смешивание бизнес-логики с логикой хранения).

Как это выглядит на практике? Начнем с примера:

import StorageManager from 'storex'
import { DexieStorageBackend } from 'storex-backend-dexie'

const storageBackend = new DexieStorageBackend({
    dbName: 'my-awesome-product'
})
const storageManager = new StorageManager({
    backend: storageBackend
})
storageManager.registry.registerCollections({
    user: {
        version: new Date(2018, 11, 11),
        fields: {
            identifier: { type: 'string' },
            isActive: { type: 'boolean' },
        },
        indices: [
            { field: 'identifier' },
        ]
    },
    todoList: {
        version: new Date(2018, 7, 11),
        fields: {
            title: { type: 'string' },
        },
        relationships: [
            {childOf: 'user'} # creates one-to-many relationship
        ],
        indices: []
    },
    todoListEntry: {
        version: new Date(2018, 7, 11),
        fields: {
            content: {type: 'text'},
            done: {type: 'boolean'}
        },
        relationships: [
            {childOf: 'todoList', reverseAlias: 'entries'}
        ]
    }
})
await storageManager.finishInitialization()

const user = await storageManager.collection('user').createObject({
    identifier: 'email:[email protected]',
    isActive: true,
    todoLists: [{
        title: 'Procrastinate this as much as possible',
        entries: [
            {content: 'Write intro article', done: true},
            {content: 'Write docs', done: false},
            {content: 'Publish article', done: false},
        ]
    }]
})
# user now contains things generated by underlying backend, like ids and random keys if you have such fields
console.log(user.id)
# You can also use MongoDB-like queries
await storageManager.collection('todoList')
  .findObjects({user: user.id})

Что мы делаем, так это определяем схему ваших данных и отношения между ними в виде графа (используя childOf, singleChildOf и Connects, подробнее см. В документации). Затем мы можем выполнять различные операции чтения / записи на база данных, которая делегируется серверной части, которая может преобразовать ее в идиоматические способы более низкого уровня для работы с базовой базой данных. (В этом случае бэкэнд - это IndexedDB через клиентскую часть Dexie, но также доступен бэкэнд Sequelize для взаимодействия с серверной базой данных SQL. Вы можете внести свой вклад в бэкэнд Firebase здесь.) Идея заключается в том, что каждая серверная часть (и плагины) может реализовывать свои собственные операции с пространством имен, в то время как те, которые могут совместно использоваться множеством различных баз данных (createObject, transaction и т. д.), тщательно спроектированы и стандартизированы. Существует стандартный механизм обнаружения функций, поэтому ваш код может автоматически определять, может ли ваш код работать поверх новой серверной части или адаптироваться к различным наборам функций (например, прямой полнотекстовый поиск или необходимость во внешней базе данных полнотекстового поиска. .) А если ничего не помогает, каждая база данных позволяет вам напрямую обращаться к низкоуровневому соединению.

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

import { SequelizeStorageBackend } from ‘storex-backend-sequelize’
const storageBackend = new SequelizeStorageBackend({sequelizeConfig: {host, username, password, database: ‘my-awesome-product’}})
const storageManager = new StorageManager({ backend: storageBackend })

Кроме того, серверная часть Dexie поддерживает сверхбыстрый полнотекстовый поиск на стороне клиента с использованием настраиваемого стеммера, который в настоящее время используется в расширении браузера Memex для запоминания всего, что вы видите, и ваших заметок об этом, что позволяет вам искать все свои знания без ущерба для вашей конфиденциальности! Моя любимая переносимость между клиентской и серверной стороны - это улучшение моего рабочего процесса разработчика за счет запуска всего моего приложения в памяти на стороне клиента, поэтому мне не нужно запускать серверную часть во время разработки и я могу инициализировать базу данных с помощью фикстур. легко разрабатывать определенные функции с определенным набором данных. Или, конечно, разработка на основе тестов для вашего клиентского приложения непосредственно из командной строки с использованием базы данных в памяти, которая затем будет работать и на стороне клиента с помощью Dexie.

Решение распространенных проблем, связанных с вашими данными

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

Одна из таких проблем - миграция схем. Помимо уровней абстракции SQL, каждая технология, похоже, имеет свое собственное решение. Разве не было бы замечательно, если бы мы могли просто описать, как должна выполняться миграция, и чтобы все было достаточно модульным, чтобы мы могли выполнить ее с помощью одной строки кода, сгенерировать сценарий SQL, сгенерировать функцию Firebase или, возможно, даже использовать это для преобразования данные импортированы из файла или получены по сети? Так работает storex-schema-migrations. Он разделен на несколько подпакетов, которые вычисляют разницу между двумя схемами, генерируют миграцию и могут ее выполнить. Однако вы можете выполнить эту сгенерированную миграцию самостоятельно, чтобы вы могли преобразовать ее в сценарий SQL для передачи администратору баз данных или сделать с ним другие вещи. Или используйте схему diff самостоятельно, чтобы визуализировать изменения схемы. Или, может быть, вы хотите применить их к своим приборам модульного тестирования, чтобы вам не приходилось постоянно обновлять их вручную.

В дорожной карте есть много других функций, которые мы реализуем по мере необходимости. Вот некоторые из них:

  • Синхронизация источников данных: при работе в одноранговой сети или создании автономных приложений, когда нескольким устройствам необходимо синхронизировать свои автономные изменения с облаком, вам необходимо записывать изменения, воспроизводить их. , и разрешать возникающие конфликты. Это может быть довольно сложная проблема, но это обычная проблема, для которой не существует ничего независимого от базы данных, что означает, что каждый должен продолжать решать эту проблему, что приводит к большому количеству дерьмовых оффлайн-первых. (Это действительно высокий приоритет для Memex, когда люди хотят искать свою историю на нескольких устройствах.) Вы можете поделиться своими мыслями, вариантами использования и требованиями здесь.
  • Контроль доступа: при работе с источником данных, совместно используемым разными пользователями, вам необходимо решить, кто и к чему получит доступ. Помимо разработки системы разрешений для каждого нового приложения, способ, которым это делается, кардинально меняется при переходе от Backend as a Service или любой другой системы, которая хочет управлять вашими разрешениями, к тому, когда вы реализуете свой собственный REST / GraphQL API. В идеале вы должны иметь возможность декларировать свои разрешения и применять их гибкими способами. Эта идея объясняется далее здесь.
  • Автоматические серверные API: ли действительно так важно, где находится код, который обращается к вашей базе данных? В идеале вы должны иметь возможность свободно перемещать этот код между интерфейсом и сервером, не заботясь о том, что на самом деле перемещает ваши данные. Идея здесь состоит в том, чтобы снова иметь возможность написать логику хранилища с некоторыми аннотациями и просто автоматически сгенерировать сервер API и потребителя. Эта функциональность должна быть спроектирована таким образом, чтобы вы могли при необходимости настраивать макет API, но иметь разумные значения по умолчанию, будучи достаточно модульной для переноса между различными фреймворками HTTP (Express, Koa и т. Д.) Или разделения вашего API на разные микро- Сервисы. (Примите участие здесь, хотя некоторые новые идеи должны быть там задокументированы.) Наличие этого позволило бы вам просто разрабатывать свое приложение полностью в своем браузере для быстрой итерации, имея при этом, какой бэкэнд и как ваши микросервисы просто разделены. является вариантом конфигурации.
  • Регистрация ваших операций. Подобно подготовленным операторам, это позволит вам регистрировать, какие операции вы собираетесь выполнять, в одном месте. Это позволило бы серверным модулям оптимизировать перевод в низкоуровневые запросы, имея при этом одно место, где у вас есть обзор того, как ваше приложение взаимодействует с вашими данными. Это позволяет вам, например, лучше понять, где разместить индексы, как сегментировать данные, какие данные читают и обрабатывают операции более высокого уровня (это связано с контролем доступа), а также генерировать очень полезную аналитику о производительности базы данных.
  • Живые миграции между разными базами данных: ваш стек будет продолжать развиваться по мере роста и развития вашего приложения. Это означает, что в какой-то момент вы собираетесь переместить свои данные в другую базу данных. Также популярно начать разработку приложения с использованием BaaS и создать собственную серверную часть, когда вы начнете масштабироваться, чтобы сэкономить на затратах. Это включает запись сразу в две базы данных и другие уловки. Для этих приемов должен быть код, чтобы сделать процесс максимально простым и надежным.

Следующие шаги

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