Лучший шаблон дизайна для объединения таблиц базы данных
Я изучаю шаблоны программирования/проектирования для слоя модели своего приложения, и мне интересно, какой из них лучше всего подходит для ситуации, когда вы делаете поиск, который включает в себя объединения между несколькими таблицами.
Например, предположим, что у вас есть следующие таблицы/отношения: Клиент → 1..n Учетные записи → 0..n Особенности
где Особенностью может быть чековая книжка или какой-то премиальный продукт, такой как бесплатное страхование путешествий.
Затем я хочу сделать getCustomersForFeature(), чтобы получить всех клиентов с учетными записями, которые имеют бесплатное страхование путешествий.
Использование ActiveRecord или объекта доступа к данным, похоже, не подходит, поскольку они обычно сосредоточены на одном классе за таблицу; аналогично для Data Mapper. Я понимаю, что могу разбить его на операции для каждой таблицы, например. getAccountsForFeature() и getCustomersForAccount(), но я хочу сделать поиск одним ударом.
Если мы должны "сгибать" шаблоны одного класса за стол и использовать шаблон объекта доступа к данным, скажем, должен ли метод getCustomersForFeature() использовать CustomerDAO или FeatureDAO? Но мне это не кажется правильным, потому что вы будете загрязнять ваши DAO знаниями других таблиц.
Предложения пожалуйста.
Ответы
Ответ 1
В Model-Driven Design у вас есть логические объекты в вашем приложении, и эти объекты могут сопоставляться с несколькими таблицами в физической базе данных. Конечно, вам нужно запустить более сложный SQL, чтобы получить полный Entity, а класс Model - это место, где эти отношения реализованы.
Я думаю, что ActiveRecord и их файлы отлично подходят для простых запросов к одной таблице, но попытка принудительного использования этих шаблонов для сложных запросов слишком сложна. К счастью, у нас уже есть сжатый, специфичный для домена язык, который можно использовать для задания сложных запросов: SQL.
Итак, в вашем классе Model у вас есть методы для выполнения логических задач на уровне приложений, таких как getCustomersForFeature()
. В коде для этого метода вы должны написать конкретный запрос либо с помощью методов ActiveRecord, либо с помощью прямого SQL, если это необходимо. Таким образом, конкретный дизайн вашей базы данных инкапсулируется в одном месте в классе Model.
Это означает, что нам нужно разбить связь между Моделью и Таблицей. Связь OO между классом и классом ActiveRecord не является IS-A - она HAS-A (или имеет-много).
Ваш комментарий: Так что же наша модель? Если вашему приложению в первую очередь нужно работать с клиентами как сущности и рассматривать функции как более или менее атрибут клиентов, то да, ваша модель будет Клиентами, и это скроет тот факт, что функции хранятся в отдельной таблице в базе данных, Модель Customers внутренне использует либо ActiveRecord, либо простой SQL для сбора необходимых данных, чтобы предоставить полный обзор сложного объекта, который является клиентом с его связанными многозначными атрибутами.
Однако, что, если ваше приложение также нуждается в работе с функциями напрямую? Например, экран администратора, на котором вы можете получать отчеты на основе функций или создавать новые функции. Тогда было бы неудобно обращаться к функциям через модель Customer. Поэтому в конце концов вам понадобится модель Feature. Только у него были бы разные методы для выполнения операций, которые вам нужны для функций.
Каждый класс модели должен выставлять API только того, что вам нужно сделать с этой моделью. Нет необходимости даже симметрично. Просто потому, что ваша модель Customer может получить всех клиентов, у которых есть данная функция, не обязательно означает, что ваша модель модели должна извлекать все функции для данного клиента. Следуйте правилу YAGNI.
Но после того, как вы создали модель Customer и модель ваших функций, не приводит ли это к дублированию логики, которая знает о связях между таблицами? Да, возможно. Это одна из многих проблем, подпадающих под вопрос несоответствие объектно-реляционного импеданса.
Ответ 2
Я потратил некоторое время на чтение по этому предмету (включая мини-книгу Domain Driven Design, указанную в ответе Билла). В этой книге есть сводная концепция, которая наиболее точно отражает то, что я пытаюсь достичь; Клиент инкапсулирует и контролирует доступ к учетной записи и функции. Если мы придерживаемся подхода, управляемого доменом, мы могли бы использовать репозиторий для управления поиском Клиента, и мне кажется, что именно здесь будет инкапсулировано знание структуры базы данных.
У меня также был другой взгляд на мою копию шаблонов архитектуры корпоративных приложений, и, хотя кажется, что шаблон ActiveRecord предназначен для прямого сопоставления таблицы класса с базой данных, если вы решили не следовать методу Repository из DDD, тогда Data Mapper подходит для композитного отображения.
Спасибо всем за ваш вклад. Я голосовал за ответы, которые способствовали этому выводу. Поскольку это не может быть последним моментом в этом обсуждении, я отметил этот ответ как Community Wiki.
Ответ 3
Способ Active Record in Rails заключается в том, что позволяет объекту Customer иметь _Many Accounts, что в основном переводит на коллекцию объектов Account, которые, в свою очередь, имеют набор функций. Эти отношения могут быть двунаправленными, поэтому каждая модель AR может "знать" об этом, в зависимости от ваших потребностей.
Я думаю, что для объекта хорошо знать другие таблицы, поскольку такие отношения имеют фундаментальное значение как для OO, так и для RDBMS/SQL.
Ответ 4
В С#/Linq to SQL это будет примерно следующее. (Я предполагаю, что на самом деле есть таблица поиска типов объектов, так что у вас есть стандартный список ваших типов объектов, а затем их отношение к учетной записи является отдельным, поэтому FeatureTypeId будет вашим значением FK, возможно, выбранным из раскрывающегося списка список или что-то еще.)
// or whatever data type your keys are in
public IEnumerable<Customer> getCustomersForFeature(int featureTypeId)
{
return from feature in dbContext.Features
where feature.FeatureTypeId == featureTypeId
select getCustomer(feature.Account.Customer.Id);
}
На каком-то уровне ваш DAO/BAO должен знать отношения между вашими объектами, поэтому тот факт, что это отношение к бабушке и дедушке, не должно быть слишком страшным.
Что касается того, где он принадлежит вашей структуре BAO, аргумент, вероятно, можно было бы сделать в любом случае. Я бы, вероятно, поместил его в Customer, так как в конечном итоге я пытаюсь добраться до него.
Изменить: Как заметил Тоби, отношения двунаправлены; снова в Linq, вы можете пойти другим путем:
// or whatever data type your keys are in
public IEnumerable<Customer> getCustomersForFeature(int featureTypeId)
{
return from customer in dbContext.Customers
from account in customer.Accounts
from feature in account.Features
where feature.FeatureTypeId == featureTypeId
select getCustomer(customer.Id);
}
Любой другой ORM должен иметь очень схожее поведение, даже если синтаксис и структура будут меняться.
Ответ 5
В целом, моделируйте свои данные в соответствии с возможностями используемой вами базы данных SQL. Игнорировать ORM, Mappers и т.д.
Как только у вас будет хорошая модель данных, если ActiveRecord или DAO не будут обращаться к ней так, как вы хотите, тогда обходите их, написав SQL.
Объектное реляционное сопоставление должно облегчать программирование с базами данных, но поскольку или различия между моделями, иногда простой способ - перейти прямо к SQL.
Ответ 6
Согласовано с GalacticCowboy, что ORM позволяет отделить данные сопоставления классов базы данных от объектно-ориентированных запросов. Просто хочу предоставить пример для java hibernate orm - существует множество способов определить сопоставления ассоциаций и после объектно-ориентированного запроса HQL работает отлично все из них
выберите c из Customer c left join c.accounts left join a.features f, где f.name = 'cool'
Обратите внимание, что "Клиент" - это имя класса, а "c.accounts", "a.features" и "f.name" - это имена свойств класса, то есть здесь не упоминаются детали, специфичные для базы данных.
Ответ 7
Я думаю, что это классическая проблема (по крайней мере для меня), где вы хотите представить свою модель данных в своем приложении. По моему собственному опыту всегда есть аккорды, как вы описали.
Мое решение очень похоже на некоторый ответ выше. Сделайте простой класс, который один к одному со своей таблицей, и если эта таблица имеет отношения к другой таблице, сделайте как свойство в своем классе.
И про ваше высказывание: "Я хочу сделать поиск одним ударом"., Я думаю, вы должны написать собственный SQL-запрос или, возможно, использовать LINQ.
Ответ 8
В то время как Object для реальных вещей отлично подходит для поддержки таблицы типов CRUD, я не нахожу ничего битового реального SQL, когда речь идет о таблицах сложных запросов.
ORM-модель - это просто абстракция слишком далеко.
Ответ 9
Требования oten решают это для вас. Либо к вашему сроку, вашей культуре работы, либо вашим требованиям к проекту. Ваш крайний срок может указывать на "код для инструментов" и вырезать функции. Вашим сотрудникам может потребоваться "никаких новых библиотек или отказов". В моей собственной среде вы услышите, как я расскажу эту историю:
"... он не стал бы вводить новые библиотеки ORM, поэтому они не работают. Крайние сроки указывают на то, что я не должен начинать преподавать себе, как создавать представления SQL. Моя рабочая культура информирует меня о том, чтобы сделать это быстро. Моя архитектура стонет от сериализации целых таблиц, и мои требования к производительности указывают, что я не должен кэшировать какие-либо из этих данных..."
Представления SQL могут обеспечить хороший способ абстрагирования объединений от ваших объектов и могут позволить вам изменить вашу схему больше независимо от вашего кода приложения. Обновление представлений очень сложно, в зависимости от вашей базы данных. Если важна переносимость DB, это, вероятно, значительно расширяет ваш дизайн.
Вы можете найти гармонию с гибридным подходом. Если вы используете табличный шлюз, нет причин расколоть его в каждом представлении уровня приложения в таблице. Почему бы не использовать табличные шлюзы или активные записи для обновления элементов в области таблицы и пользовательские классы, которые относятся к запросам, ориентированным на представление?