Вопрос Обзор
Как @leanne сказал, вы моделирующие Subscription
, чьи специализации, скажем, MonthlySubscription
и ComplimentarySubscription
(дать им название для этого ответа).
Вы знаете, что подписка может истечь:
- Для
MonthlySubscription
, что происходит, когда пользователь не оплатил подписку в текущем месяце
- Для
ComplimentarySubscription
, срок годности назначается, когда он назначен пользователю
Как вы можете увидеть ExpirationDate
является неотъемлемым атрибутом любого Subscription
, но так, как вы храните его различен в каждом конкретном случае. Если в первом случае вам придется вычислять его на основе последнего события, в последнем вы можете получить его напрямую.
Работа с наследованием в базе данных
Итак, чтобы отобразить этот пример модели в схеме базы данных, вы могли бы пойти с Class Table Inheritance узором, описанным в книге Patterns of Enterprise Application Architecture Мартина Фаулера. Вот его цель:
«Представляет иерархию наследования классов с одной таблицей для каждого класса».
В принципе, у вас будет таблица с атрибутами, совместно используемыми между классами, и вы будете хранить атрибуты, относящиеся к каждому классу в отдельной таблице.
Имея это в виду, давайте рассмотрим опции, предложенные:
- Есть еще один стол
complimentary_subscription
с идентификатором пользователя в качестве внешнего ключа?
Имея отдельную таблицу для хранения ComplimentarySubscription
конкретных деталей звучат хорошо, но если вы не связать это один с subscription
столом, вы можете в конечном итоге с пользователем, который имеет как MonthlySubscription
и ComplimentarySubscription
. Его внешний ключ должен указывать на таблицу subscription
, в которой указывается, есть ли у пользователя подписка или нет (и вам нужно будет применять до одной подписки на пользователя).
- Запись специальный "подписка" для них в
subscription
?
Да, вы должны записать, что пользователь имеет подписку ежемесячно или бесплатный. Но если вы думаете что-то вроде записи специальной подписки, количество которой равно нулю, вы ищете решение, соответствующее вашему текущему дизайну, вместо поиска подходящей модели для него (возможно, и это может быть не так).
- Или добавить еще один столбец их пользовательской строки для столбцов, как
is_complimentary
и complimentary_expires_date
?
Лично мне не нравится этот, потому что вы помещаете информацию, где она не принадлежит. Принимая это во внимание, где вы будете хранить дату истечения срока действия бесплатной подписки (помните, что для ежемесячных вы ее вычисляете и не сохраняете дату истечения срока действия)? Кажется, что вся эта информация плачет за собственный «дом». Кроме того, если вам нужно добавить новый тип подписки, таблица начнет мешать.
- Добавить более общий
expires
столбец в строке пользователя?
Если вы сделаете это, вы будете иметь дело с синхронизацией данных каждый раз, когда subscription_event
получает изменилось (в случае ежемесячной подписки). Обычно я стараюсь избегать этой ситуации дублирования данных.
Пример решение
Что я буду делать в пользу расширяемости при добавлении новых типов подписки иметь subscription
таблицу для хранения общих деталей между MonthlySubscripton
и ComplimentarySubscription
, добавив ключ в type
столбце, который будет позволить вам дифференцировать, с какой подписью связана строка.
Затем укажите данные, относящиеся к каждому типу подписки, в своей собственной таблице, ссылаясь на родительскую строку subscription
.
Для получения данных, вам нужен объект, отвечающие за инстанцирование правильного типа Subscription
заданного значение type
столбцов для subscription
строки.
Вы можете посмотреть на картины в «Узоры архитектуры предприятия Application» книги для дальнейшей помощи в том, как определить type
значения столбцов, как использовать объект сопоставителя сделать Subscription
экземпляра и так далее.
01/03/2012 Обновление: Альтернативы для определения и обработки type
колонки
Вот обновление прояснить следующий вопрос, отправленный @enoinoc в комментариях:
В частности, для предлагаемого столбца type
это может быть внешний ключ, указывающий на таблицу Plans
, которая описывает различные типы подписки, например, сколько месяцев назад они истекают без оплаты. Это звучит логично?
Это нормально, чтобы иметь такую информацию в таблице Plans
, поскольку это не статическая информация, которую не нужно редактировать. Если это так, не делайте чрезмерного обобщения своего решения и помещайте это знание в соответствующий подкласс Subscription
.
О внешнем ключе подхода, я могу думать о некоторых недостатках, идущих таким образом:
- Помните, что ваша цель состоит в том, чтобы узнать, Wich подкласс
Subscription
использовать для каждой строки в таблице Plans
. Если все, что у вас есть, это значение внешнего ключа (скажем, целое число), вам нужно будет написать код для сопоставления этого значения с классом, который будет использоваться. Это означает, что вам нужна дополнительная работа :)
- Если вам нужно делать ненужную дополнительную работу, вполне вероятно, что основное внимание будет иметь боль: каждый раз, когда вы добавляете новый план, вам нужно будет запомнить hardcoding, это значение внешнего ключа в код отображения.
- Внешние ключи могут измениться в случае неправильных операций экспорта/импорта базы данных. Если это произойдет, ваш код сопоставления больше не будет работать, и вам придется снова развернуть свое программное обеспечение (или, по крайней мере, измененную часть).
Предлагаемое решение
То, что я хотел бы сделать это, чтобы положить в type
колонку в Plans
таблице. В этом столбце будет указано имя класса, который знает, как правильно построить Subscription
из определенной строки.
Но: зачем нам нужен объект для построения каждого типа Subscription
? Потому что вы будете использовать информацию из разных таблиц (subscription_event
и complimentary_subscription
) для создания каждого типа объектов, и всегда полезно изолировать и инкапсулировать это поведение.
Давайте посмотрим, как Plans
таблица может выглядеть следующим образом:
- Планы Таблица -
Id | Имя | Тип | Другие колонны ...
1 | Ежемесячно | MonthlySubscriptionMapper
|
2 | Бесплатные | ComplimentarySubscriptionMapper
|
Каждый SubscriptionMapper
можно определить метод Subscription MapFrom(Row aRow)
, который принимает строку из базы данных и дает вам правильный экземпляр подкласса Subscription
(MonthlySubscription
или ComplimentarySubscription
в примере).
Наконец, чтобы получить экземпляр картографа, указанного в type
колонка (без использования неприятной if
или case
заявления) вы можете взять имя класса от значения столбца и, с помощью отражения, создать экземпляр этого класса.
Hi @eoinoc. Что значит для пользователя иметь подписку «бесплатный доступ»? Какие атрибуты имеют общие с регулярными подписками и какие атрибуты имеют важное значение для «бесплатного доступа»? – nick2083
@ nick2083 Это означает, что мы можем предоставить им доступ к системе на уровне членов, но им не нужно платить, в отличие от обычного члена, который должен платить ежемесячно по подписке. – eoinoc