Pull to refresh

Проектирование баз данных. Паттерн Компоновщик (Composite)

Reading time4 min
Views16K
Web 2.0 победоносно шагает по виртуальному миру. Социальные сети растут как грибы после дождя. Теперь в одном месте вы можете хранить свои фото, видеозаписи, писать блоги и слушать музыку. Все это можно комментировать, класть в избранное, копировать… Возможностей много, контент социальных сетей разнородный и разнообразный, и в этом их преимущество.

А теперь представьте себе структуру БД какого нибудь «Вконтакте». Представили? И что вы видите? Множество таблиц с данными? А что еще? Множество таблиц для связей много-ко-многим! Необходимых, с точки зрения реляционной БД, но лишних с точки зрения логики. Но это еще не все. Среди полей таблиц мы видим огромное количество «лишних» полей, являющихся всего лишь внешними ключами, служащими для связей один-ко-много, так же необходимых с точки зрения реляционной теории, но абсолютно бесполезных с точки зрения логики.

Представьте теперь что вы решили создать свою социальную сеть. Вы планируете что в ней будут пользователи (куда ж без них?). Эти пользователи смогут объединятся в группы, хранить фото и видео и писать блоги. Представив структуру БД мы поймем что вроде бы ничего сложного. Так оно и есть, если структура связей проста. Но ведь требования могут усложниться. Теперь заказчику нужно что бы контент мог принадлежать нескольким пользователям одновременно, а заодно и сообществу. Что бы все это (любой объект) можно было комментировать. Что бы в избранном пользователь мог хранить вообще все что вздумается, в том числе и других пользователей и сообщества и блоги сообществ. Да мало ли что можно придумать еще! И каждое такое требование влечет за собой перепроектирование базы, добавление новых таблиц, внешних ключей. Я уже не говорю о написании программного кода. А можно ли как нибудь от этого избавиться? Как-нибудь сделать так, что бы связи между любыми объектами (читай записями) можно было создавать произвольно, не перепроектируя каждый раз БД.

Можно! Открываем книжку «Банды четырех», находим описание паттерна Composite и читаем:

Назначение

Компонует объекты в древовидные структуры для представления иерархий часть-целое. Позволяет клиентам единообразно трактовать индивидуальные и составные объекты. (советую прочесть всю главу)


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

Итак задача данной архитектуры, позволить произвольным образом связывать объекты системы друг с другом.

В рассматриваемой архитектуре каждый объект, это запись в таблице. Для каждого типа объектов (фото, видео, пользователь и тд), в базе создается своя отдельная таблица. Каждая такая таблица состоит только из полей в которых хранятся только те данные, которые относятся к объекту + первичный ключ записи (то есть никаких внешних ключей). Связь осуществляется через единую для всех таблицу с именем GUID, состоящею их четырех полей. Почему поля четыре? Ну 2 поля понятно, это ID связанных объектов, то есть их внешние ключи. Классическая схема — много-ко-многим. Но ведь только по ID нельзя уникально идентифицировать объект в пределах всей БД. В разных таблицах ID может повторяться (секвенция для каждой таблицы своя). Поэтому введено понятие типа объекта. По сути тип объекта это и есть имя таблицы в которой этот объект хранится (или имя класса в программном коде, как хотите). И, таким образом, пара ID-TYPE являются уникальным идентификатором объекта в пределах всей базы данных. Именно поэтому таблица связей GUID состоит из 4 полей, первые 2, как я сказал выше, это ID связанных объектов, а остальные 2 это их типы. Зная ID и тип объекта (парент), можно выбрать все связанные с ним объекты (чайлды), как все, так и конкретного типа.

Это позволяет строить самые причудливые связи, и очень гибко реагировать на изменение бизнес требований к системе.

Какие недостатки я вижу в такой архитектуре. В первую очередь, это огромная таблица GUID. Ведь мин количество записей в ней равно количеству записей во всех таблицах БД. И это только минимальное, так как объект может быть связан сразу с несколькими другими объектами (впрочем, разделить на несколько таблиц в будущем не проблема).
Второе, это немного более тяжелые запросы. Если связь один-ко-многим реализована по классической схеме то выборка будет лишь по внешнему ключу, а в такой модели внешнего ключа нет, и приходится джойнить таблицу GUID

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

Ну и несколько слов о реализации. Писать запросы руками никто, конечно, не хочет, поэтому вся логика работы с БД вынесена в отдельный слой, называемый GuidComponent. Это базовый класс, представляющий собой некое подобие паттерна ActiveRecord. Все объекты системы наследуют его (например PhotoComponent, VideoComponent и т.д.) Базовый класс, GuidComponent, предоставляет каждому наследнику методы для работы с другими объектами системы, для связывания и удаления связей, для получения данных объекта и его связанных объектов (Чайлдов). Например для привязки объекта достаточно вызвать метод addChild(); и передать в качестве параметра ссылку на связываемый объект.

Вот краткая диаграмма классов (немного устаревшая правда):



Ну и в заключение, пример кода на Java, добавляющего комментарий к фотографии:
//Добавляем комментарий к фотографии с id = 10
PhotoComponent photo = new PhotoComponent(10);
CommentComponent comment = new CommentComponent();
comment.setMessage("текст комментария");
comment.save();

photo.addChild(comment);

//Получаем все комментарии к фото
List comments = photo.loadChilds(CommentComponent.class);


Ну вот так примерно, в общих словах. На самом деле все немного (честное слово немного) сложнее, тут я не рассказал еще о параметризованных и связанных выборках, но цель статьи раскрыть суть идеи, а не предложить готовое решение. Если эта идея кого-то заинтересует, я готов продолжить публикацию, и глубже раскрыть тему, вплоть до рабочего кода базового класса.
Tags:
Hubs:
+43
Comments98

Articles

Change theme settings