Как стать автором
Обновить

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

Время на прочтение4 мин
Количество просмотров16K
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);


Ну вот так примерно, в общих словах. На самом деле все немного (честное слово немного) сложнее, тут я не рассказал еще о параметризованных и связанных выборках, но цель статьи раскрыть суть идеи, а не предложить готовое решение. Если эта идея кого-то заинтересует, я готов продолжить публикацию, и глубже раскрыть тему, вплоть до рабочего кода базового класса.
Теги:
Хабы:
Всего голосов 51: ↑47 и ↓4+43
Комментарии98

Публикации