Концептуальные проблемы с IndexedDB (отношения и т. Д.) - PullRequest
28 голосов
/ 01 января 2012

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

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

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

Прямо сейчас, я имею в виду концепцию, которую я действительно боюсь начать реализовывать.Я думал о создании хранилища объектов для каждой таблицы в базе данных сервера и программировал объекты отношений в разных хранилищах объектов вручную.В моем приложении, которое, короче говоря, управляет курсами университета, у меня будет 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": { /* 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 для моей проблемы.1046 * Редактировать:

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

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

1 Ответ

21 голосов
/ 15 января 2012

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

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

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

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

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

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

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

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

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

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

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

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

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, я просто еще не уверен, насколько это будет практично.

Удачи!

...