Концептуальные проблемы с IndexedDB (отношения и т.д.)

Я пишу диссертацию об автономных способностях веб-приложений. Моя задача - показать возможности автономного хранения через веб-приложение с серверной реляционной базой данных и Ajax/JSON-трафиком между клиентом и сервером. Моя первая реализация использовала подход с localStorage, сохраняя каждый ответ Ajax как значение с URL-адресом запроса в качестве ключа. Приложение работает отлично. На следующем шаге, однако, я хочу (то есть, тезис требует) реализовать более продвинутую версию с клиентской базой данных. Поскольку сервер поддерживает реляционную базу данных, Web SQL Database была бы интуитивным выбором. Но, как мы знаем, стандарт устарел, и я не хочу использовать технологию, будущее которой неопределенно. Таким образом, я хочу использовать IndexedDB для реализации логики базы данных на стороне клиента. К сожалению, после прочтения большого количества материалов в Интернете, которые в основном хранят очень много царапин на поверхности (todo-notes applications и т.д.), Я до сих пор не знаю, как действовать.

Моя задача выглядит довольно простой: реализовать серверную базу данных на клиенте с помощью IndexedDB для репликации всех данных, которые были однажды получены с сервера. Проблемы, которые делают это намного менее прямолинейными:

  • База данных на стороне сервера является реляционной, IndexedDB является (более или менее) объектно-ориентированной
  • Нет интуитивного способа синхронизации баз данных на стороне клиента и на сервере
  • Нет интуитивного способа реализации отношений в IndexedDB, которые реализованы с использованием внешних ключей и JOIN на сервере

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

Я хочу продемонстрировать свою идею с примером для ответа JSON с сервера (/* это комментарии */):

{ "course": { /* course object */
    "id":1, 
    "lecturer": { "id":"1", /* lecturer object with many attributes */ },
    "semester": { "id":"1", /* semester object with many attributes */ }, 
    /* more references and attributes */
}}

Алгоритм хранения данных с помощью IndexedDB сохранит каждый объект, который применяется к хранилищу объектов в соответствующем хранилище объектов, и заменит объекты ссылками на эти объекты. Например, вышеуказанный объект курса будет выглядеть следующим образом в хранилище объектов "курс":

{ "course": { /* course object */
    "id":1, 
    "lecturer": 
    { "reference": { /* reference to the lecturer in the object store 'lecturer' */
        "objectstore":"lecturer",
        "id":"1" }
    },
    "semester":
    { "reference": { /* reference to the semester in the object store 'semester' */
        "objectstore":"semester",
        "id":"1" }
    }
    /* more references and attributes */
}}

Алгоритм извлечения данных с помощью IndexedDB будет делать следующее (у меня есть рекурсивный шаблон смутно):

Retrieve the course object with id=1 from the object store 'course'
For each reference object in the retrieved course object, do
   Retrieve the object with id=reference.id from the object store reference.objectstore
   Replace the reference object with the retrieved object

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

Как я могу сделать это лучше и проще?

Я уже посмотрел на эти потоки, которые представляют схожие проблемы: link1, link2. Я не вижу в них более простых решений. Более того, я бы предпочел избежать использования инфраструктуры обертки IndexedDB из-за нескольких причин.

Я мог бы также предположить, что я полностью ошибаюсь с IndexedDB для моей проблемы.

Edit:

Наконец я закончил свой подход, чтобы хранить ссылки в самих объектах в IndexedDB. Это может привести к некоторым проблемам с производительностью в случае больших объемов данных со многими ссылками. Однако, если их использовать разумно, в большинстве случаев можно избежать огромных количеств итераций и удалений базы данных, и нет необходимости хранить сложную схему базы данных в памяти или в самой IndexedDB.

Как правило, я должен сказать, что у меня создается впечатление, что я неправильно интерпретирую динамическую и прямую идею с IndexedDB как базу данных схемы. Но что бы то ни было, я реализовал все на JavaScript, он отлично работает и нет никаких шансов на какие-либо несоответствия.

Ответ 1

Я новичок в IndexedDB, но я тоже много думал о том, как использовать IndexedDB для таких целей. Первое, что я хотел бы предложить, если вы еще этого не сделали, - это посмотреть, как работают другие базы данных ключа/документа (CouchDB, MongoDB и т.д.), Поскольку это, по сути, тип базы данных, в которой указан IndexedDB.

Существует несколько различных подходов к обработке отношений в базе данных документов... что касается синхронизации с вашей реляционной серверной базой данных, вам, вероятно, потребуется создать какое-то настраиваемое сопоставление, поскольку некоторые из подходов к отношениям, которые смысл для IndexedDB не будет очень хорошо отображаться в реляционной базе данных. Тем не менее, я думаю, что создание такого сопоставления, безусловно, выполнимо, и большая проблема заключается в том, как обрабатывать отношения в IndexedDB, так что я сосредоточусь здесь...

Что касается вашего предлагаемого решения, я думаю, что он действительно может работать хорошо, и вы могли бы написать простую библиотеку запросов, которая помогла бы консолидировать сантехнический код (подробнее об этом ниже). Хранилища с ключевыми значениями построены так, чтобы быть очень эффективными при поиске элементов по ключевым словам, поэтому для каждого связанного объекта может быть не так малоэффективно, как вы думаете... однако, я придумал еще одну идею, которая лучше использует индексы...

Во-первых, для моего предлагаемого решения вам нужно будет хранить метаданные "objectstore" где-то иначе, чем внутри самого "ссылочного" объекта... его вообще не обязательно нужно хранить в IndexedDB; вы можете просто использовать для этого схему памяти:

var schema = {
    Course: {
        fields: [id, title],
        relationships: {
            lecturers: {objectstore: 'lecturer'},
            semester: {objectstore: 'semester'},
        }
    },
    Lecturer: { ... }
    ...
};

(Кстати, ваш пример JSON имеет ошибку... у вас не может быть более одного ключа с именем "reference" - он должен быть массивом "ссылок".)

Это освобождает вас, чтобы хранить значения идентификатора непосредственно в полях отношений, чтобы вы могли создавать индексы на них (я использовал префиксы писем для ясности, хотя на самом деле все они, вероятно, имели бы идентификатор 1, поскольку значения ID не обязательно должны быть уникальными в разных магазинах):

var course1 = {
    id:'C1',
    lecturers:['L1'],
    semester:1
};

var lecturer1 = {
    id:'L1',
    courses:['C1']
}

var semester1 = {
    id:'S1',
    courses:['C1']
}

Разумеется, вы должны быть осторожны, чтобы все операции хранения/поиска выполнялись через функции доступа к данным (например, insert(), update(), delete()), которые были достаточно умны, чтобы гарантировать, что отношения всегда обновлялись правильно на обоих концах... на самом деле вам может и не понадобиться, что в зависимости от того, как вы планируете запрашивать данные, но это кажется хорошей идеей, поскольку иногда вы можете просто хотеть получить идентификаторы связанных объектов (для поиска позже, или нет), а не фактически извлекать их.

Скажем, у вас есть указатель на поле "курсы" в магазине лектора. Используя индекс, вы можете искать всех лекторов, связанных с определенным идентификатором курса одним махом:

lecturerStore.index("courses").get("C1").onsuccess = …

В этом случае это не имеет большого значения, потому что в курсах обычно будет только 1-2 преподавателя, но рассмотрите, как индекс можно использовать для эффективного поиска всех курсов в конкретном семестре:

coursesStore.index("semester").get("S1").onsuccess = …

Обратите внимание, что в примере лектора (отношение "многие ко многим" ) индекс должен быть указан как "multientry", что означает, что если у вас есть поле, значение которого является массивом, каждый элемент массива будет быть добавлен к индексу. (См. https://developer.mozilla.org/en/IndexedDB/IDBObjectStore#createIndex... Я не уверен, что поддерживает браузер.)

И я уверен, что вы могли бы делать и другие умные вещи с индексацией, используя курсоры и IDBKeyRange, чтобы помочь выполнить какую-то операцию объединения. Для идей ознакомьтесь с этой ссылкой, которая демонстрирует способы обработки отношений в CouchDB:

http://wiki.apache.org/couchdb/EntityRelationship

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

(Кстати, я не уверен, насколько это было бы полезно для вас, поскольку он не обеспечивает большого количества запросов, но кто-то действительно реализовал базу данных CouchDB поверх IndexedDB: https://github.com/mikeal/pouchdb)

В дополнение к индексам реализация механизма кэширования, вероятно, тоже поможет.

Теперь, чтобы упростить процесс запроса, я знаю, что вы упомянули о том, что не хотите использовать библиотеку-оболочку... но у меня возникла идея о удобном API, который можно было бы создать, чтобы принять такой объект:

//select all courses taught by 'Professor Wilkins'
{
from: 'lecturer',  //open cursor on lecturer store 
where: function(lecturer) { return lecturer.name=='Professor Wilkins' }, //evaluate for each item found
select: function(lecturer) { return lecturer.courses }, //what to return from previous step
//this should be inferred in this case, but just to make it clear...
eagerFetch: function(lecturer) { return lecturer.courses }
}

Я не уверен, насколько сложно было бы реализовать, но, похоже, это облегчит жизнь.

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

Удачи!