Комментарии 262
Зачем ER? Делаете классы и связи между ними и вот вам ER. А по ней автоматом генерируется схема, если она вообще нужна вашей СУБД.
Разве не так все работают?
Вы скажете, что это извращение. Возможно, но в дизайне баз данных ещё и не такие извращения иногда доводится крутить, при этом, что характерно, оставаясь в рамках высочайших стандартов нормализации данных.
Ни одна ORM не потянет мэппинг двух классов на одну таблицу.
Вполне потянет в общем случае. Конкретный кейс, когда, кажется, таблица состоит исключительно из primary key составного — наверное, нет. Но с суррогатным идентификатором потянет объект типа "Запись книжки" с объектными (ValueObject) свойствами "Абонент" и "Номер".
Прочувствуйте пожалуйста красоту идеи ситуационно-зависимого проецирования набора фактов на объекты, с которыми работает логика и пользователи. С одной стороны зашли — один набор объектов, с другой стороны зашли — другой набор, с третьей — третий. На одних и тех же фактах. Данные в базе хранятся естественным для них образом. Объекты выстраиваются опять же естественным для них, объектов, образом. То, что одно другому один-в-один не соответствует — да и не очень-то хотелось. ORM требует, чтобы соответствовало, и в результате у нас или база кривая, или миллион строк невменяемого кода, или и то, и другое вместе. Ребята, зачем?
ORM требует, чтобы соответствовало,
Конкретное ORM требует, чтобы соответствовало, все существующие ORM требует чтобы соответствовало или само понятие ORM требует, чтобы соответствовало?
шайтан-фреймворк сам допиливает метаданные БД.
Насколько мне известно, это далеко не всегда так
что классы «Абонент» и «Номер» нужно отмэппить на одно и то же отношение, он решит, что разраб сошёл с ума и откажется так
Если я сделаю фреймворк, который не откажется, почему его нельзя будет назвать ORM?
Да не откажется в нормальной ORM. Может в лоб не получится, но получить классы «Абонент» и «Номер» на базе одной таблицы вполне возможно.
Почему лишняя, если в реальности она "физически" существует в виде кортежа реляции? Я решал задачу маппинга кортежей реляций на объекты.
А так вам никто не мешает результаты запроса типа SELECT abonent FROM phone_abonent WHERE abonent = 'Вася'
маппить на VO "Абонент" без инстанцирования объектов «Запись книжки». А вот если захотите выполнить INSERT средствами ORM, то будьте добры инстанцировать как в SQL вы задаёте кортеж.
P.S. Без суррогатного ключа можно обойтись, PK не обязан быть примитивного типа.
Почему лишняя, если в реальности она «физически» существует в виде кортежа реляции?Физически мы все состоим из атомов, но функционируем как единое целое. Аналогия понятна?
Как построить объекты для приведённого издевательского примера про имена и номера телефонов? Полезные объекты — Абонент (у которого может быть несколько номеров) и Номер (который может использоваться несколькими абонентами).
Так и делаете. Класс "Абонент" (id, name, ...) и класс "Номер" (number, ...), первое поле — первичный ключ. Между ними связь "Абонент — Номер". И такие же таблицы в базе. У вас "таблица в базе одна" потому что вы объединили их в одну, нарушив правильную модель. Представьте, что кроме имени у абонента еще и адрес есть, или номер офиса. Что у вас будет в одной таблице? Правильно, куча дублирующейся информации и возможность ее несоответствия в разных дублях. Из-за этого и появляются всякие костыли в коде типа "Нужно только исключить ситуацию, когда пользователь под именем «Маша» записывает десять разных Маш. Решается на уровне пользовательского интерфейса". При правильной модели этих проверок вообще нет.
Объекты "Запись книжки" в коде это массив указателей на объекты "Абонент" в объекте класса "Телефон" или "Телефон" в объекте класса "Абонент". Вы в принципе не сможете без них обойтись, даже если будете делать без БД.
Компьютер не является Тьюринг-полным, у него память ограничена. Даже в отрыве от конкретной железки, например, в C/C++ указатель — тип фиксированного размера (не важно что стандарт языка не специфицирует этот размер, важно что он конечен), поэтому они принципиально неспособны адресовать бесконечную память.
И проблемы останова у компьютеров нет. Любая программа, запущенная на компьютере, рано или поздно перестанет выполняться.
Вы всё же недооцениваете Объектные СУБД. Почитайте, хотя бы, это: https://habr.com/ru/post/267079/
Даже проще: буде носклы сколь конкурентоспособны в нише субд_для_делания_денег уже давно ораклы и майкрософты имели/купили бы подобный продукт. Суть в том, что как только речь идет не про удобно, а про производительность и всякий хайлоад, то носкл базам предъявить особо нечего… собсно поэтому то и мы возвращаемся к п.1 — для поиграться майкрософты внедрили элементы носкл и этом успокоились.
Ну а про графориентированные или как их раньше называли сетевые то это даже в учебниках 70х годов писали что это наиболее мощные базы. Просто не было их реализаций доступных какое то время. Сейчас появились по крайней мере три orientdb, arangodb, neo4j. Последняя имеет сложное лицензирование и скорее всего для многих именно поэтому не подойдёт. Вторая из них отлично работает но только пока данные помещаются в оперативную память. А вот orientdb очень даже удобная штука. Конечно ничего не могу сказать о ее надёжности. Опыта в этом смысле нет.
Поэтому NoSql вполне находят свою нишу. Но она очень специфична. Поэтому крупные сенсоры «не заморачиваются», но вводят костыли типа функций для работы с json и xml.
Для подавляющего типа задач этого достаточно. А если нет… То есть свободные NoSql. А на бесплатном трудно заработать.
-7 у статьи какбэ намекает
На что же намекает?
уже давно ораклы и майкрософты имели/купили бы подобный продукт
Это если предполагать, что все их решения рациональны и направлены исключительно на улучшение жизни своих клиентов.
Как по мне, то не из-за сомнений, а из-за отсутствия сомнений, безапеляционном способе подачи своей точки зрения. Хотя в последнее время, кажется, вместо " не нужен", чаще встречается "зачем нужен , если можно...?" :)
Какой мой тезис не был подкреплён аргументами?
Извините, но не буду просматривать ваши комментарии за несколько лет, чтобы показать вам, что у моего впечатления о вашей манере общаться тут, есть основания. И не всегда то, что человеку кажется просто убойным аргументом в его правоте, является таковым для остальных.
Между "вы не предъявляете аргументов" и "мне не нравятся ваши аргументы" — целая пропасть. Первое — легко проверяемый факт. Во втором же случае — поди разберись кто из нас двоих дурак.
А сейчас вы даёте мне характеристику, безапелляционно не подкрепляя свои слова фактами. И сами же признаёте, что на выискивание подобных фактов вам потребуется просмотреть несколько тысяч комментариев. То есть "безапелляционность" моих утверждений как минимум не очень частое явление.
Неудобства разработки они скорее лежат в плоскости restapiесли оно используется, фронтенда. А orm скорее стабилизирует разработку.
Хотя могу согласиться что когда начинается разработка структуры с выделением сущностей а не по простому нарисую как структура базы. То часто получаются химеры.
Очень часто модель строят по тому как ему удобно, а не так как эффективно движку базы. Потом начинаются рефакторинги сего добра, и дай бог чтобы миграции не загнулись.
Главная проблема ORM это непредсказуемость и как результат — вероятные проблемы с производительностью, но это плата за удобство. Вопрос в том в том, что важнее в каждом конкретном случае.
Проблема в том что определенные подходы работы ORM требуют дополнительных телодвижений с базой.
UPDATE — перед тем как изменить рекорд его надо загрузить из базы потом апдейтгуть.
DELETE — те же грабли
INSERT — получим дополнительный селект после вставки, или если умная ORM, то сделает INSERT WITH OUTPUT. Сказать чтобы вообще ничего не тянуло нельзя.
Можно продолжать список
UPDATE — перед тем как изменить рекорд его надо загрузить из базы потом апдейтгуть
Не во всех ORM это обязательно. Проблемы тут возникают только на массовых операциях.
Сказать чтобы вообще ничего не тянуло нельзя.
А зачем?
такое мощные, как hibernate, sqlalchemy и т.д. вполне себе предсказуемы и гибки.
А что не так со схемой БД, построенной по объектной модели? Можете привести примеры боли с производительностью?
В итоге можно легко получить БД которая даже первой нормальной форме не соответствует. К примеру люди добавляют в БД человека и вкладывают в него данные о документах или данные о том где он живет.
А где здесь нарушение первой нормальной формы? ORM либо "раскроет" вложенные объекты, либо создаст связанные таблицы. В обоих случаях первая нормальная форма будет соблюдена… благодаря ORM.
Дополнительно очень часто такой подход рождает не оптимальные запросы к БД так называемая проблема N+1...
… решаемая в EF уж тремя разными способами (проекции, eager loading и explicit loading)
А где здесь нарушение первой нормальной формы? ORM либо «раскроет» вложенные объекты, либо создаст связанные таблицы. В обоих случаях первая нормальная форма будет соблюдена… благодаря ORM.
Прям в автомате без указания, что это отдельные таблицы и что тут связи? Не припомню не одного. Обычно всегда надо сделать отдельное и указать это будет отдельной сущностью тут будет ссылка. Т.е. скатываемся на уровень БД.
… решаемая в EF уж тремя разными способами (проекции, eager loading и explicit loading)
Которые явно надо указывать. По умолчанию будет именно N+1. В итоге опять скатываемся на уровень БД.
Как бы я говорю о тех кто использует ORM в режиме «я ничего не хочу знать о БД пусть ORM за меня сделает». Это обычно приводит к плачевным результатам. Когда же люди помнят о наличии БД, то заметно проще сначала сделать бд, а потом отобразить ее в объекты.
Прям в автомате без указания, что это отдельные таблицы и что тут связи? Не припомню не одного.
EF — да. Любой класс, не помеченный явно как составной тип, считается отдельной сущностью, а свойства этого типа — связями 0..1 ко многим.
Которые явно надо указывать. По умолчанию будет именно N+1.
Неа, по умолчанию будет null
(при выключенной ленивой загрузке)
А ленивая загрузка по умолчанию выключена? Что-то мне подсказывает что нет. Чаще всего как раз нет.
А ленивая загрузка по умолчанию выключена?
В EF 6 — нет (но не вижу проблем её взять и выключить), в EF Core её просто нет.
Кстати, ленивая загрузка недоступна если свойство не виртуальное, чего "по умолчанию" не делают.
Потом рожают крокодилов, которые генерят такие запросы, что DBA при изучении начинает пить валидол.
А зачем DBA изучать эти запросы?..
Раз уж дошло дело до подключения DBA — надо выкидывать запрос, и писать хранимку.
Очень часто это решается банальным включением жадной загрузки. И другими простыми рекомендациями по изменению схемы работы ORM
Так зачем изучать этих крокодилов-то? Можно и без изучения начать применять те рекомендации. А если не помогло — то начать писать хранимку, опять-таки без изучения крокодилов.
И да как вы будете хранимку уже в существующее приложение подпихивать и как это поможет решить проблему?
И да как вы будете хранимку уже в существующее приложение подпихивать
Изменением исходного кода, конечно же. Точно так же этого требуют любые рекомендации по ускорению ORM.
Да, это можно записать в недостатки, хотя я считаю преимуществом.
и как это поможет решить проблему?
В хранимке DBA сможет написать запрос в любом виде, используя любые техники оптимизации которые ему подсказывают опыт и explain.
Если мы рассматриваем ситуацию "куча человек без знания SQL + 1 DBA" — то писать запрос в любом случае именно ему и придется.
Вопрос лишь в том, сразу ли он будет писать все запросы (и вот тогда-то он, в будущем, и сможет просто добавить пару хинтов) — или же часть запросов сгенерирует ORM, а ему останется лишь переписать оказавшиеся неоптимальными.
Проблема в запросах которые пишут хипстеры без знания SQL.
Наинклюдают неглядя с пол базы, хотя надо конкретно небольшие части трех таблиц.
Если ORM генерирует предсказуемые операторы SQL то не могу представить как те же самые операторы SQL выполненные из ORM будут выполняться медленнее. Хорошие ORM например вычисляют разницу и генерируеют только реально изменившиеся поля в запросах а также формируют в одном запросе несколько опрераторов т.к. в данном случае операторы обновления выполняются по ленивой схеме — сначала накапливаются в буфер а потом сохраняются в базу.
Что касается разработки «от объектов» или как любят говорить «от сущностей» я в последнем абзаце комментария на который Вы ответили как раз и отметил что при разработке абстрагируясь от схемы данных (таблиц, индексов) «мысля объекатми» можно настроить разных химер что очень часто и случается с разработчиками которые не изучали хотя бы поверхностно теорию РСУБД и думают что с ORM это совсем необязательно.
Что касается химер, чисто ИМХО, они появляются в тот момент, когда некие сущности из-за особенности языка опускаются из описания. Например, тот же указатель в C++ — это «связь 1-1_либо_много», точный тип которой нельзя получить из контекста, а сам указатель не имеет собственного объекта и\или состояния. Поэтому реализовать в C++ ORM средствами лишь самого языка нельзя, а любые попытки впихнуть компромиссный вариант натыкаются на боль и унижение (на выбор, тонны макросов или работа только с небольшим подмножеством языка). Единственное решение — избавиться от сырых указателей и явно прописывать этот самый объект. А оно не самое тривиальное.
Как говаривал Козьма Прутков (по памяти): «Многие вещи недоступны нашим понятиям не оттого, что понятия наши слАбы, а оттого, что вещи сии не входят в круг наших понятий.».
Простите, навеяло…
В смысле стандартов C и C++, кстати, указатель как раз объект — хранящаяся по какому-то адресу значение (адрес чего-то другого).Если выражаться терминами стандарта C++… Но ведь это не верно по сути, поскольку указатель, как и многие другие типы в C++, обладает бо́льшим числом свойств, чем хранит фактически. Ну, например, настоящий тип, валидность объекта по адресу, факт владения, факт разделения владения, число указателей на объект и тп. Они являются свойствами указателя, свойствами адреса, но!.. Адрес — не объект, наоборот, у объекта есть свойство «адрес». А указателям нужно подключать std::shared_ptr\std::weak_ptr для того, чтобы эти свойства явно иметь. И даже так, многие проблемы остаются нерешёнными.
P.S. Можно ниже устроить срач на тему «Является ли валидность (и тп) объекта по адресу свойством указателя, свойством адреса или даже самого объекта?». Тема богатая для срачей, но абсолютно бессмысленная по сути, поскольку в самом языке указатель на любой адрес, отличный от nullptr, является валидным. То есть, с точки зрения языка не существует невалидных объектов. Просто некоторые из этих валидных объектов могут быть не очень съедобными для среды выполнения. По стандарту это так, а по сути — мы все знаем, как.
P.P.S. Да, я знаю, что валидность объектов по адресу гарантируется только при условии получения этого адреса от new или оператора &. Но вам ведь ясно написали «гарантируется». Это не значит, что нельзя его получать из malloc или 100500[myArray]. Или вообще наплевать на это дело и использовать численные значения, сидя в микроконтроллере.
А таблица в базе — всего одна, задающая отношение многие-ко-многим между множеством имён и номеров телефонов.
А это не не значит, что сведения, например, об имени абонента будет в нескольких записях таблицы? Как там будет с нормализацией?
Несколько записей по одному и тому же имени абонента — нормальная ситуация. Нужно только исключить ситуацию, когда пользователь под именем «Маша» записывает десять разных Маш. Решается на уровне пользовательского интерфейса.
Несколько записей по одному и тому же имени абонента — нормальная ситуация.
Да, но нет. Несколько записей (с разными номерами, предполагаем что номер является ключом, то есть уникален) с одинаковым именем абонента — это информация о номерах… разных абонентов! Если только у вас нет отдельной таблицы, в которой ключом является уже имя и первая таблица не связана с ней по внешнему ключу.
Какого типа колонка "имя абонента"? Есть ли у абонента что-то кроме имени и телефонов? Что происходит если в имени абонента нашли опечатку или его надо переименовать?
Немного конкретизирую ситуацию В нашей таблице должно быть ровно две колонки: номер телефона и имя. Т.к. если мы захотим сохранить еще информацию о чем-либо то скорее всего она будет зависеть только от телефона или только от имени и это будет явным нарушением нормальной формы.
Но что же со случаем когда ровно два поля: телефон и имя. Пусть Маша сменила имя и стала Мария. Во что выливается такое изменение: нам нужно выбрать все строчки Маша и присвоить им имя Мария. Уже есть подозрение что нарушена нормализация Собственно так и есть. Если сформулировать то получается что поле «имя» в таблице зависит от значений поля «имя» этой де таблицы. Честно говоря я не осилил формы дальше 3-й хотя как правило разрабатываю оптимальные структуры данных Но кажется это многозначная зависимость и нарушение 4нормальнйо формы. А так же есть еще 6 нормальная которая говорит о том что прока декомпозиция возможна это еще не 6-я нормальная форма.
Немного хочу обратить внимание что хотя я и не нашел этого в определениях Кодда и Дейта, но в одном из наших старых учебников по информатике ко всем определениям НФ была добавлена фраза "… с учетом выполняемой задачи". То есть например атомарность это неделимость данных в связи с выполняемой задачей. И т.д. Поэтому чисто формально первичный ключ по двум уникальным полям есть. А вот когда начинаешь разбирать то что связано с задачей то понимаешь что форма хотя и нормальная но в каких-то начальных уровнях.
Базовую идею ООП можно попытаться сформулировать примерно так: поскольку мир состоит из объектов, то его, этот мир, было бы удобно моделировать созданием объектов внутри программной системы.
Ну зачем так передёргивать? Человек рассматривает окружающий мир как совокупность объектов, поэтому программировать ему тоже может быть удобней при помощи объектов.
Дальше, впрочем, не лучше.
Есть отличная статья в Википедии: Object-relational impedance mismatch. Там собрана серьёзная, качественная критика ORM:
- SQL чужда инкапсуляция,
- SQL не предполагает той связи между данными и процедурами (существительным и глаголом), которая является характеристической чертой ООП,
- ООП подразумевает, что объект имеет уникальный идентификатор, а результат запроса по сути своей не может однозначно идентифицироваться,
- и т. д.
Но уважаемый maslyaev вместо критики предлагает нам набор соломенных чучелок:
- ООП − это плохо, поэтому ORM − тоже плохо. Однако ООП широко применяется на практике, поэтому нам придётся с ним считаться. Распространить тот же принцип практической применимости на ORM автор почему-то не хочет,
- ORM не является проблемно-ориентированным, поэтому не нужен,
- задачу независимости программы от конкретной СУБД уже решали раньше (на самом деле ODBC весьма хреново решает эту задачу по сравнению со многими ORM, ну да ладно),
- абстрактный мэппинг − это слишком сложно, мы лучше сделаем много ситуационных мэппингов, типа «средний объём продаж за заданный период», и будем потом весь этот код поддерживать,
- и т. д.
Разумеется, список проблем, которые решают (успешно, на практике) ORM у автора далеко не полон. Вот только наиболее очевидные для меня:
- безопасность запросов (в смысле изоляции от пользовательского ввода),
- оптимизация обращений к СУБД (каскадное конструирование запроса, отложенное выполнение),
- миграции!
Лично мне прочувствовать пользу ORM на практике помогло написание нескольких небольших проектов на Python без ORM. Начав писать свой абстрактный уровень поверх MySQLdb, я понял, что занимаюсь ерундой. Сравнивая Django ORM, SQLAlchemy или PonyORM с тогдашними моими экзерсисами, я понимаю, что ORM − не кошмар и не гиря на ноге, а реальные помощники в работе, пусть и не идеальные.
Что касается соломенных чучелок, то не очень понимаю, почему Вы увидели только эти Вами перечисленные. Главное чучелко — это то, что невозможно по-настоящему крепко подружить математику и не-математику. ООП, кстати, это совсем даже не плохо. Я этого не говорил. Сам им с удовольствием пользуюсь. Просто не надо его абсолютизировать и возводить в догму. Не годится оно для этого.
безопасность запросовИнъекции? Ну, не знаю. Как-то совсем давно не вписывал значения в прикладном коде прямо в SQL-строку, отправляемую на сервер. Что с ORM, что без. Как-то сразу приучил себя предохраняться.
оптимизация обращений к СУБД (каскадное конструирование запроса, отложенное выполнение)Чисто по практике: рефакторинг с отказом от ORM зачастую в разы улучшает производительность именно за счёт более оптимизированной работы с СУБД.
миграции!А что миграции? Вы о чём? Об ETL или об автоматическом апдейте структуры базы?
ООП моделирует графы. А графы — тоже раздел математики.
Главное чучелко — это то, что невозможно по-настоящему крепко подружить математику и не-математику.Что вы имеете в виду под не-математикой? ООП разве не построено на основе теории множеств?
Как-то сразу приучил себя предохраняться.Так ведь и не надо приучать, если можно пользоваться инструментами, исключающими ошибки.
Чисто по практике: рефакторинг с отказом от ORM зачастую в разы улучшает производительность именно за счёт более оптимизированной работы с СУБД.Разумеется, у меня нет такого опыта − переписывать проект с выбрасыванием из него ORM, я даже не слышал о таких проектах, да и не стал бы в таком участвовать.
Мой практический опыт говорит мне, что, во-первых, на один неоптимальный запрос, полученный с помощью ORM, приходится сотня запросов, которые ORM сгенерирует оптимальнее, чем программист с первой попытки, и уже этим использование ORM оправдывает себя. Во-вторых, отдельные запросы всегда можно оптимизировать, и тут обычно есть выбор: улучшить код, использующий ORM, либо включить «сырой» SQL в конкретный запрос. Об отказе от ORM в обоих случаях речи не идёт. Ну, и, в-третьих, у любой оптимизации есть границы применимости. А то ведь можно всё на Ассемблере переписать, без SQL, будет ещё быстрее.
А что миграции? Вы о чём? Об ETL или об автоматическом апдейте структуры базы?Второе.
ООП разве не построено на основе теории множеств?Нет, не построено. В первом приближении класс — это множество, а экземпляр — это элемент. Но если копнуть глубже, начинаются чудеса. Например, оказывается, что у нас с операциями пересечения и объединения множеств? Вот есть, например, два класса — Customer и Vendor. Как их будем объединять и пересекать? Какие-то странные множества. Теория множеств, в которых определена только операция разбиения на подмножества (наследование). Немножко странно всё это.
Вот я, например, пару недель назад учинил рефакторинг структуры базы (очень хотелось разогнать в пару десятков раз). Написание весьма хитрой миграции у меня заняло пару часов. Почему-то уверен, что через ковыряние во всяких миграционных тонких настройках в духе джанговского каталога «migrations» отняло бы гораздо больше времени, сил и нервов, и всё равно в результате не дало бы той стопроцентной уверенности, которую я получил вручную.Об ETL или об автоматическом апдейте структуры базы?Второе
Вот есть, например, два класса — Customer и Vendor. Как их будем объединять и пересекать?
Это называется «множественное наследование».
class Сounterparty(Customer, Vendor):
pass
Вот я, например, пару недель назад учинил рефакторинг структуры базы
Писать код не надо:
$ ./manage.py makemigrations && ./manage.py migrate
Вообще, контрагент — это покупатель ИЛИ продавец. А у Вас "И".
Что касается makemigrations/migrate, то оно по-любому не потянуло бы мою сложную переукладку данных. Всё равно пришлось бы собственно перенос данных рисовать вручную, и делался бы он не групповыми запросами, а пообъектно. То есть в десятки раз дольше. Или сотни.
Вообще, контрагент — это покупатель ИЛИ продавец. А у Вас «И».
Ну, с «ИЛИ» вроде нигде проблем нет: это их общий базовый класс. Я думал, вы имеете в виду «И».
Что касается makemigrations/migrate, то оно по-любому не потянуло бы мою сложную переукладку данных. Всё равно пришлось бы собственно перенос данных рисовать вручную, и делался бы он не групповыми запросами, а пообъектно. То есть в десятки раз дольше. Или сотни.
Если нужно как-то небанально преобразовывать данные, то мы так же делаем `makemigrations` и `migrate`, только между ними мы вписываем в миграцию только тот код, который работает с данными. И не обязательно работать «пообъектно», любые запросы также доступны в этом коде, можно использовать «пакетный» режим (limit/offset) и т. д. И при этом всё правильно оборачивается в транзакции, так что не нужно думать об обработке исключений. Всё это в любом случае упрощает работу и не заставляет идти на компромисы по скорости.
Вообще то контрагент — это Покупатель И/ ИЛИ Продавец И/ИЛИ Сотрудник. :)
Вот есть, например, два класса — Customer и Vendor. Как их будем объединять и пересекать
В общем, если сделать шаг в сторону от джавового ООП, то вот так (typescript):
class Customer {}
class Vendor {}
type CustomerAndVendor = Customer & Vendor;
type CustomerOrVendor = Customer | Vendor;
Это какая-то совсем чёрная магия. Для контроля типов и составления контекстной подсказки сгодится, но какое ожидать поведение от таких объектов?
"Продавец и покупатель" обладает всеми свойствами как продавца, так и покупателя, и может выступать в обоих ролях.
Если такое поведение невозможно (конфликт методов или свойств) — ну, значит, такой тип не имеет смысла. Бывает.
"Продавец или покупатель" может быть как первым, так и вторым, и обладает методами и свойствами, общими для этих двух типов.
Собственно, все как ожидается.
Собственно, все как ожидается.
Кому как :) От | я ожидаю что он обладает полными свойствами хотя бы одного из них, что ложный результат проверки на тип Customer гарантирует, что это тип Vendor.
Все так и есть. Без проверки доступны только общие методы, с проверкой — методы того типа, на который проверились. Проверять можно обычным if-ом, а можно написать type guard.
В обоих случаях компилятор все понимает, и в теле if-а тип выводит сам.
Так и есть. Ну, с поправкой на то, что проверку, по=хорошему, нужно делать структурную ("утиную"), а не номинативную.
Например очень интересная работа есть books.google.com.ua/books/about/%D0%92%D0%B5%D1%89%D0%B8_%D1%81%D0%B2%D0%BE%D0%B9%D1%81%D1%82%D0%B2%D0%B0_%D0%B8_%D0%BE%D1%82%D0%BD%D0%BE%D1%88.html?id=q7EIAwAAQBAJ&redir_esc=y
А.И. Уемов Вещи, свойства и отношения
Там те же вроде бы понятия но их интерпретация основана на другой базе
doctrine-migrations (кстати, не является частью DoctrineORM) генерирует скрипт миграции на PHP, в котором чистый SQL конкретной версии конкретного диалекта SQL.
1) сравнивается всегда реальная база данных с объектной моделью
(другой вариант который мне встречались: в проекте хранится формализованное представление схемы данных kalax(go), sequelize-migration(kavascript)) — профит в том что из любого состояния базы данных можно сделать правильную миграцию. При реализации с сохраненной схемой все сложнее. Если внести неправильные ручные изменения в миграцию или же два разработчика сделают свой вариант миграции от одного коммита, или же в данные будут внесены изменения вручную (не обязательно по структуре данных например можно для ускорения работы создать индекс) то схемы удалятся друг от друга очень быстро и очень далеко
2) генерируется скрипт на php. профит — можно добавлять свои дополнительные операторы в зависимости от задачи. например, присвоить значения в добавленную колонку, или заполнить данными новую созданную таблицу если из текущей таблицы была часть столбцы выделена в новую связанную таблицу. Варианты которые я еще встречал: генерируется просто скрипт sql который конечно можно кастомизировать но не получится сделать некоторые действия которые доступны из языка программирования. Еще другой вариант: генерируется условная схема миграции например {table:user, action: add-column: {name: address, type: text}} и т.п. В этом случае понятно что ни о какой кастомизации уже не может быть речи
3) это собственно следствие второго: возможность кастомизации. Профит — собственно сама кастомизация. Как противоположный случай можно представить закрытый от доработок метод sync() — который за Вас «все сам делает» но как-то непредсказуемо все это
Я лишь указал, что doctrine-migrations не генерирует миграции на каком-то своём языке. Она формирует своё внутренне (PHP runtime) представление как желаемой схемы, так и текущей, и на базе диффа генерирует по сути SQL в PHP обёртке. Декларативный подход к генерации миграций, а не императивный. Про удобство этого подхода я прекрасно знаю :) Про недостатки тоже :(
ООП разве не построено на основе теории множеств?
Чаще вместе с ООП упоминают абстрактные типы данных (ATD), где упор на операциях (поведении, глаголах), связанных со значениями. Реляционная модель фокусируется множествах фактов и логических отношениях между ними. Но на практике ни кто не мешает забивать шурупы микроскопом, использовать RDBMS как хранилище из-за ACID изкаропки, а ООП для тул для реюзинга кода.
Самые популярные нотации реляционной модели обычно игнорируют семантику логических отношений. Им без разницы, отношение "является", "принадлежит" или "состоит".
Термины логика и отношения перегружены разными смыслами. Вы кажется говорите про онтоллгии. Здесь же имееются ввиду отношения как в логике счисления предикатов — конъюнкция, дизъюнкция и вот это все. Реляционная алгебра это по сути обобщение ЛСП на множества, что позволяет оперировать не отдельными утверждениями, а сразу пачками подобных (отношениями).
Засада с ORM в том, что когда мы говорим сохранить объект в базу, то имеем ввиду сохранить нечто логичное в терминах ER, но нелогичное в терминах ООП. В ООП объект это в первую очередь поведение, состояние которого напрочь скрыто от внешнего наблюдателя (инкапсулировано). Поведение описывается программой. Но сохраняя объект мы не сохраняем базу поведение, а как раз вот это самое неведомое состояние.
ORM сочетает принципиально разные вещи. Поэтому тут нет универсального подхода. В зависимости от решения, эти модели могут как органично дополнять друг друга, так и страшно неудобно конфликтовать.
Чисто по практике: рефакторинг с отказом от ORM зачастую в разы улучшает производительность именно за счёт более оптимизированной работы с СУБД.
Отказ от ORM вообще (то есть результаты запрос на объекты не маппятся никак) или от универсальных? Ну и не для всех производительность (в разумных пределах) важный критерий. причём важный настолько, чтобы оптимизировать его со старта проекта.
Что значит "на самом деле"? Ни один из трёх основных паттернов ORM не предполагает маппинг на DTO. Ну и собственно у классического DTO методов save, delete быть не может. Скорее всего вы про паттерн ActiveRecord, а он подразумевает, что объект является бизнес-объектом с дополнительной с возможностью себя сохранять или удалять.
Описанные вами удобства относятся к "Query Builder", который может применятся отдельно от ORM.
можно тупо поставить ->where() после ->order_by() и она соберет все как надо.
Результат конечно будет такой, как вы ждёте, и вы вроде бы сэкономите время на написании кода, но когда проектом станет пользоваться чуть больше, чем 1 тысяча человек онлайе, вы сразу почувствуете просадку, потому-что сначала база возвращает в ORM все результаты от order by, а далее уже чаще всего исполняемый код делает все постпроверки where, вместо того, чтобы это делала база данных. Таким образом напрягается и база данных и сеть и сервер, который пересчитывает результаты, вместо всего одной БД, которая быстрее посчитала бы всё у себя в памяти.
Про IN я вообще молчу. В двух реализациях orm я видел, как она сначала делает select *, а потом пихает в IN все сотни тысяч полученных id в гигантский where in (1,2,3,4,5...), вместо простейшего left join.
Как итог — вы либо лезете в исходники изучать, почему orm делает такие странные запросы, либо гуглите, и находите с 10го раза правильный пример. Либо плюете, и пишете обычный и правильный SQL запрос.
Миграции — это отдельная тема. Очень странно, что ещё не изобрели нормальный git для структур бд.
Миграция/эволюция БД и ORM — ортогональны, много инструментов для первого обходятся без второго.
Хвалить ORM за то, что он позволяет не знать SQL и «лепить» запросы «на отвали» — тупиковое направление, ИМХО.
ORM чертовски удобен в типовых несложных случаях типа CRUD для нехитрого мастер-детали.
РДБ с их упором на уникальность фактов с их максимальной декомпозицией на атрибуты — ох, не всегда хороши. Иногда гораздо удобней «подшивать бумажки к делу» в документ-ориентированное хранилище: документ как контейнер, служит более эффективной заменой многим джойнам и связям. Но да, надо следить за тем, чтобы подшивать бумажки в правильные дела…
Как по мне, то как раз в сложных областях, которые плохо ложатся на CRUD без ORM очень грустно (при условии, что области хорошо ложится на ООП). В простых CRUD вполне можно на SQL писать.
Для меня ещё один плюс ORM — код структурируется естественным образом, благодаря тому, что привязывается к уже хорошо структурированной реляционной базе. В ORM-объектах удобно делать единую входа точку для той или иной обработки данного объекта, и от этого с одной стороны сразу становится понятно где что в коде искать, а с другой стороны — меньше шансов выстрелить себе в ногу, когда мы в одном месте логику поменяли, а в другом — забыли.
Оптимизации запросов же можно делать и средствами ORM.
Некоторые ORM подразумевают создание схем БД на основании объектной модели как основной кейс. А в общем случае задача ORM в том, чтобы связать две независимо созданные структуры: схему БД и объектную модель. Увы, не всегда это получается, ORM обычно протекают минимум в одну сторону: или в объектной модели торчит наружу реляционная природа, или в схеме базы странные вещи творятся с точки зрения нормальных форм и т. п. А иногда в обе стороны.
А можете какой-нибудь пример привести? В программе объекты соединяются через указатели, в таблице через первичные ключи. Вроде всегда можно задать однозначное соответствие?
Например, в объектной модели у класса Order(заказ) есть свойство типа массив lines со строками заказа типа товар+количество. В объектной модели строке ссылка на заказ не нужна, как и идентификатор самой строки (достаточно индекса строки в массиве). Не во всех SQL базах вы сможете напрямую создать в таблице order столбец lines, являющийся упорядоченным множеством пар [itemId: quantity], а если сможете, то работать с ним будет неудобно, могут, например, отсутствовать возможности полноценной индексации такого поля, да и условие типа SELECT * FROM order WHERE EXISTS(order.items.itemId = 10523) вряд ли напишите.
Скорее всего вы создадите таблицу order_items, может с суррогатным PK, может с составным [orderId, index], причём эти колонки будут в любом случае, и скорее всего суррогатный id и поля orderId и index будут в вашем объекте, хотя на уровне объектной модели они вам не нужны вообще. Протечка реляционной модели в объектную налицо.
как и идентификатор самой строки
У нее всегда есть идентификатор, это адрес в оперативной памяти. id это такой же адрес, только в более высокоуровневом логическом пространстве. Он присутствует в объекте как свойство, да, потому что убрать его совсем невозможно, так как надо работать с этим логическим пространством из приложения (select/insert/update/delete), или передавать объекты в сериализованном виде в другую систему (например на фронтенд). Это неотъемлемая часть модели, возникающая из-за дополнительного уровня абстракции. Работать с ним как с обычным свойством не совсем правильно, но это надо менять работу всех инструментов, которые с ним взаимодействуют.
то работать с ним будет неудобно, могут, например, отсутствовать возможности полноценной индексации такого поля, да и условие типа SELECT * FROM order WHERE EXISTS(order.items.itemId = 10523) вряд ли напишите.
Так и в коде будет всё то же самое, если предположить, что оперативная память бесконечная, и мы всё делаем объектами. Точно так же надо будет делать отдельный список, где будут все возможные lines.
У нее всегда есть идентификатор, это адрес в оперативной памяти.
Даже сишный указатель вроде не должен считаться адресом в памяти, это деталь реализации, просто чаще всего выбирают простой маппинг указателей 1:1 (с учётом типа данных) на память — так проще реализовать.
id это такой же адрес, только в более высокоуровневом логическом пространстве. Он присутствует в объекте как свойство, да, потому что убрать его совсем невозможно, так как надо работать с этим логическим пространством из приложения (select/insert/update/delete), или передавать объекты в сериализованном виде в другую систему (например на фронтенд)
Убрать его (высокоуровневый идентификатор строки, суррогатный или составной) совсем возможно вполне, если не допускать протечки реляционной модели через абстракцию ORM. ООП языки имеют свои способы идентифицировать объекты, отдельное свойство им для этого не нужно, а сам способо, опять же, деталь реализации. ORM может хранить карту "ссылка на объект <=> значение PK строки" у себя внутри, но это усложняет её, замедляет, и, возможно, есть ещё какие-то подводные камни, поэтому никто(?) так не делает.
Так и в коде будет всё то же самое, если предположить, что оперативная память бесконечная, и мы всё делаем объектами. Точно так же надо будет делать отдельный список, где будут все возможные lines.
Не нужно в коде так делать. Достаточно для каждого заказа иметь свой локальный список строк и тогда ссылка на ордер в строке не нужна, как и отдельное свойство для номера строки. Односторонней связи вполне достаточно. Инкапсуляция.
ORM может хранить карту "ссылка на объект <=> значение PK строки" у себя внутри
Это понятно, я имел в виду, что оно все равно присутствует в БД как обычное свойство, думал вы это называете протечкой. Вернее, это тогда тоже является протечкой.
Не нужно в коде так делать. Достаточно для каждого заказа иметь свой локальный список строк
Так а как вы будете делать то, что написали выше? Для быстрого поиска среди строк всех заказов нужен индекс, а для него надо работать со строками как с отдельными сущностями. Не перебирать же все заказы чтобы найти те, в которых есть itemId = 10523. А если есть индекс, то по строке заказа надо как-то находить указатель на этот заказ. Или какой-то особый тип индекса вводить, где сразу будут указатели на родительскую сущность вместо той, которую индексируем.
Если объект ничего не знает о столбцах PK, то это не протечка. Если ORM о них знает, то это тоже не протечка. Собственно только ORM и должна знать о PK, и вообще таблицах и столбцах в приложении.
Индексы — это оптимизации. В базе у нас тоже индексов нет по умолчанию. Ну так, да, для оптимизации исполнения такого сценарии подобный индекс нужен, но он нефункциональный, к модели как таковой не относится, как и в СУБД.
Собственно только ORM и должна знать о PK, и вообще таблицах и столбцах в приложении.
А как на фронтенде сделать выпадающий список сущностей? Там тоже id нужен.
Зачем? Индекса недостаточно?
Создаем товар, надо указать категорию. Создаем задачу, надо указать пользователя, который ее будет выполнять. Или фильтры различные.
в option мы не обязаны указывать в value идентификатор, вполне можем индекс массива, что-то вроде
{goods.availableCategories
.filter(category => category.isActive)
.map((category, index) => (
<option value={index}>{category.name} />
)
)}
Ну вот у вас поле для ввода ответственного за задачу с автодополнением, пользователь ввел "Вася", пришло 3 варианта ["Вася Иванов", "Вася Петров", "Вася Пупкин"]. Пользователь выбрал "Вася Пупкин", на сервер ушел индекс 2. Что серверу с ним делать?
Вы расширяете рамки задачи с каждым комментарием. Но в данном случае вполне можно отправлять на фронт индекс сервера вместе со значением, с ним сервер разберётся что делать, когда фронт отправит выбранный. Можно даже назвать его id, чтобы если будет задачи, для которых надо оформить значения в виде полноценных сущностей, то для фронта это прошло прозрачно.
На самом деле задача выбора пользователя уже такая задача на первый взгляд — мне сложно представить сценарии, где пользователь не является сущностью, но при этом его надо выбирать.
Можно, но зачем? Мы этим просто имитируем платформенно-независимый первичный ключ. Зачем так делать, если он уже есть, он для этого и нужен. А что если у нас 2 Васи Пупкина в разных отделах? Или добавили нового, пока пользователь задачу создавал?
Я не расширяю рамки, я же сразу написал, "передавать объекты в другую систему (например на фронтенд)". Необходимость этого означает, что о PK должна знать не только ORM, а все слои, которые с ним работают.
Название поля значения не имеет, если мы принимаем решение что полный список на фронт передавать не нужно.
Нет, расширяете. Для передачи объекта в другую систему идентификатор не нужен. Если СУБД+ORM PK нужен это вовсе не значит, что он нужен остальным. Идентификатор объекта (использование термина PK — протечка модели РСУБД в объектную бэка и фронта и ресурсную HTTP, наверное) необходим, вроде как, только для полноценных сущностей, где важно сравнение на идентичность "по ссылке". Там где необходима идентичность по значению, "PK" не просто не нужен, но вреден, поскольку приводит к неоднозначности ситуаций, когда значение PK совпадает, а остальные данные нет и наоборот.
А откуда вдруг значения взялись, разговор же был про объекты и их идентификаторы?
У объектов обычно есть три способа сравнения, проверки на идентичность:
- по "физической" ссылке в рамках ЯП
- по полю-идентификатору
- по значениям всех полей
Если говорить о коллекциях объектов, то есть ещё один: по какому-то указателю (в широком смысле слова) на место объекта в коллекции.
По значениям полей сравниваются составные значения, объекты-сущности по ним не сравниваются.
Если мы будем использовать место объекта в коллекции, то во-первых это логически ничем не отличается от автоинкрементного id, во-вторых вместо платформонезависимости id надо обеспечивать платформонезависимость коллекции, чтобы порядок везде был один и тот же. С учетом удалений и добавлений во время передачи данных. То же самое, только сложнее. Я же говорю, это в самой задаче есть, от него нельзя избавиться. Можно только заменить на аналоги, типа порядковый номер в коллекции вместо порядкового номера в таблице.
А я говорил о любых объектах, которые мы храним в БД. И это не только объекты-сущности, но и объекты-значения. Первые имеют идентификатор по определению. Вторым он скорее мешает, чем безразличен.
Логически отличается от автоинкрементного id тем, что у объектов одного типа могут повторяться индексы в разных коллекциях, а у одного и того же объекта может быть разный индекс в разных коллекциях.
Аналогично переутяжелено слово модель в MVC которая изначально в smolltalk касалась модели элемента управления на экране. Например кнопка нажата или не нажата. А теперь возведена чуть ли не в бизнес правила.
Представить можно, что угодно, но зачем представлять себе таблицу в базе данных? Не, для задач типа phpMyAdmin так и надо представлять. Но если мы пишем прикладное приложение, где есть понятие "пользователь", почему не надо его выражать объектом? То, что состояние этого объекта сохраняется в базе данных всем, кроме разработчиков и админов, всё равно по сути.
Я видел много разных ORM, но не видел ни одного хорошего.
А как же LinqToSql или Entity Framework?
Если вы используете классический вариант СУБД с жёсткой схемой данных, то эта схема уже сама по себе вносит порядок в работу с данными. Дополнительная структура, закодированная структурой объектов, просто избыточна.
А в каком виде держать данные в памяти, если вы запретили объекты?
И как же частичная статическая проверка запросов компилятором и контекстные подсказки от IDE? Избыточно и совсем не нужно?
А в каком виде держать данные в памяти, если вы запретили объекты?Это где же я запрещал объекты? Более того, я специально акцентировал внимание на том, что они — наша неизбежность.
И как же частичная статическая проверка запросов компилятором и контекстные подсказки от IDE? Избыточно и совсем не нужно?Это, конечно, вкусно, но честное слово, жить без этого можно. Впрочем, если следовать подходу «объекты как проекции фактов, а в базе факты», то будут вам и проверки компилятором, и подсказки от IDE.
EntityFramework в .NET смотрели?
Там все это реализовано.
Лично мне интересно как в разных системах (не chengelog) с переименованием сущностей справляются, победив эту проблему можно вообще migration-less ormы делать.
Как по мне, то начиная говорить об ORM или его отсутствии нужно сразу объявлять о чём конкретно говорим:
- универсальные ORM
- полное отсутствие ORM (вообще полное, результат или исходные данные запроса никогда в объектной форме не предстают, ну или предстают, но никогда колонки не маппятся на свойства, а строки на инстансы, только какие-то Query.execute.getRow(0).getColumn('id').
- комбинации, в том числе ad-hoc ORM, когда результаты запросов маппятся на объекты "ручками", хардкодом
Отдельно отделять слой DBAL, который ни является ни необходимым, ни достаточным для ORM
В одном (плюс его надмножество) есть базовый Object у массивов, в другом, где ORM как раз и использую, массивы отдельный от объектов комплексный тип данных. Но под ORM я имею в виду прежде всего маппинг столбцов таблиц на свойства объектов и наоборот, а не обёртывание таблиц и их связей в объектную модель, в которой больше от двумерного массива.
Открою тайну.
Любой ORM состоит из 3 частей:
1) Маппер (Mapper) — отображает резалтсет на объекты
2) Построитель запросов (Change Tracker) — генерирует SELECT\INSERT\UPDATE\DELETE на основе объектов и объектного языка запросов
3) Карта объектов (Identity Map + Change Tracker), которая следит чтобы одна запись в базе превращалась в один объект в памяти, а изменения объекта в памяти корректно "уезжали" в базу.
Маппер — самая примитивная вещи и проблем не представляет.
Построитель запросов — самая полезная вещь, но и самая сложная вещь в реализации
Карта объектов — самая сомнительная вещи, полезность которой постоянно оспаривается даже в этой статье.
Умные разработчики давно это поняли и просто ОТКЛЮЧИЛИ карту объектов по умолчанию в своих ORM. То есть вы строите запрос и получаете набор объектов, которые просто DTO и не имеют другой смысловой нагрузки.
Внезапно еще оказалось, что сложный мэппинг не сильно помогает. Проще создавать DTO, которые отображаю структуру базы данных. А некоторые догадались генерировать структуру на основе схемы DTO.
Уже лет 10 вся прогрессивная общественность пользуется такими легковесными ORM. Но некоторые продолжают обсуждать недостатки карты объектов и приписывать всем частям ORM.
Маппер далеко не всегда тривиален и проблем не представляет. Даже отображение 1:1 строки на объект может быть не тривиальным, если требуется одновременно сделать его и быстрым, и работающим с приватными свойствами объекта без вызова его конструктора и в остуствие геттеров/сеттеров. А если из строки таблицы нужно собрать многоуровневый граф объектов, да с возможностью поиска по любому объекту на SQL по индексу, то совсем нетривально, если вообще возможно. И это ещё не касаясь отношений между объектами в разных таблицах и наследования.
Может быть и нетривиальным. Но по факту тривиальный маппинг гораздо удобнее.
Сложный маппинг имеет накладные расходы выше и требует затрат на поддержку больше, чем положительный эффект от него.
Заметил опечатку.
Построитель запросов конечно Query Builder, а не Change Tracker
IdentityMap не является обязательной для ORM. С ней меньше одних проблем, но больше других.
ORM это отображение реляционных данных на объекты в программе. Есть отображение в любом виде, значит ORM.
Контроль единственности необязательно должен быть в самой ORM, более того, в общем случае это невозможно, потому что может быть запущено более одного инстанса приложения. Какая разница, что строка БД отображается на 2 объекта в разных процессах, в разных потоках, или в разных частях программы в одном потоке.
М.б. имеется в виду контроль единственности не объекта, а класса?
По факту разница для прикладного разработчика в том, что в рамках одного процесса мы можем устанавливать идентичность двух объектов по ссылке. А для разработчиков ORM использование IdentityMap позволяет в большинстве случаев переложить ответственность за изменения БД на прикладного разработчик. Если он перезаписывает свойства заведомо одного и того же объекта несколько раз в разных частях приложения, то не должен удивляться, что в базу запишется только последнее значение на момент вызова save или flush. А если отдавать разные объекты, то разработчика ORM придётся разруливать конфликты.
Зачем им разруливать конфликты? Я наоборот говорю, что в ORM это необязательно. Ответственность за эти изменения тоже можно переложить на прикладного разработчика. Есть средства для маппинга, как программист ими пользуется это его дело. Особенно если выносить часть функционала в отдельный процесс, какой-нибудь микросервис, то получится, что раньше все работало, а теперь не работает, потому что ORM теперь не контролирует единственность.
Чтобы улучшить привлекательность своих продуктов для разработчиков. Суть подобных продуктов — упростить жизнь прикладных разработчиков. IdentityMap в составе ORM даже неопциональная, по-моему, значительно упрощает.
А можете пример написать, где упрощает? Я довольно много работал с ActiveRecord, приложения среднего уровня, вроде не было случаев где бы это понадобилось. Обычно объект достается из базы 1 раз и передается внутри приложения по ссылке.
Ну вот представьте сущность договор, у которой есть поля создатель и подписант (ссылки на сущность "работник", например). Мы выбираем договор и получаем два разных объекта с идентичным состоянием. В чём ценность такого кейса? Как должна реагировать ORM если на запись приходит договор в котором один объект изменён (но не id), а другой нет? А если оба изменены? Да просто как-то проверить что если создатель и подписант одинаковые, то направить договор на дополнительный апрув.
Если мы редактируем договор, то изменений в объектах "Работник" быть не должно. Там вообще приходит только id работника, откуда там другие поля? Раз у нас есть платформенно-независимый id, то одинаковость проверяется через него, зачем обязательно нужно аппаратные указатели сравнивать?
1) Бизнес решает, что должно быть, а чего нет, а не мы, разработчики. Если бизнес решает, что должно, а мы отказываемся лишь потому что считаем это неправильным и не можем убедить бизнес в этом, то нас обычно увольняют.
2) Если бизнес хочет, чтобы на экране редактирования договора были указаны ФИО создателя и подписанта, то одним id вы не обойдётесь
3) "раз у нас есть" — нету, рассматриваем вопрос стоит ли его вводить и не просто вводить, но и прокидывать через все слои приложения.
P.S. Я очень не одобряю подход: "раз необходимо или почему-то есть на одном уровне приложения, значит прокинем на все" — это прямое нарушение инкапсуляции. Вот прокидываете вы умолчанию числовой id на все слои, а тут раз и появляется требование сделать его строковым да в чётком формате. Или наоборот. Все слои придётся изменять, даже если по факту это поле не используется для бизнес-логики, но используется в технических вещах типа тестов, технической валидации (в том числе средствами языка или вншнего тайпчекера) и т. п.
Если бизнес решает, что должно, а мы отказываемся
Я же не говорил, что надо отказываться. Просто кейс когда редактируются 3 сущности одновременно нетипичный. Вы сказали, что на запись приходит договор, не указав, что другие сущности редактируются на той же форме. Поэтому я предположил стандартный вариант.
"раз у нас есть" — нету, рассматриваем вопрос стоит ли его вводить и не просто вводить, но и прокидывать через все слои приложения.
А по какому критерию вы будете объекты из базы доставать, чтобы положить в IdentityMap? Она ведь задает соответствие id и указателя.
Я очень не одобряю подход: "раз необходимо или почему-то есть на одном уровне приложения, значит прокинем на все" — это прямое нарушение инкапсуляции
Так не надо его считать деталью реализации) У нас объекты независимые от платформы, передаются между разными системами, надо чтобы разные системы могли каким-то образом идентифицировать один и тот же объект. Вот и "указатели" на эти объекты такие же независимые от платформы. Это как пытаться избавиться от указателей в оперативной памяти в пределах одного приложения.
Уж что-что, а индекс в коллекции зависит от реализации практически полностью. Поменялись требования к сортировке в интерфейсе — менять надо везде.
Все слои придётся изменять
Естественно, бизнес-задача же изменилась. Но вообще именно поэтому суррогатные ключи и удобны, в бизнес-логике их нет, потому и требований к ним нет. Индекс в коллекции это и есть такой суррогатный ключ, только слишком неустойчивый к изменениям.
А по какому критерию вы будете объекты из базы доставать, чтобы положить в IdentityMap? Она ведь задает соответствие id и указателя.
На уровне базы есть. ORM о нём знает и активно использует, в том числе для IdentityMap. А дальше если он остальным слоям не нужен, то и не нужно его на них прокидывать. Ещё ситуация, когда не нужно PK прокидывать — у нас есть не только первичный (часто суррогатный) ключ, но и вторичный (естественный для предметной области). Заем вышележащим слоям знать о первичном, когда задача идентификации объектов решается вторичным на бизнес-уровне?
Вот и "указатели" на эти объекты такие же независимые от платформы.
Это не единственный способ идентификации.
Это как пытаться избавиться от указателей в оперативной памяти в пределах одного приложения.
Вы не поверите, но бОльшая часть мэйнстрим языков не имеет указателей в составе своих языковых средств. Под капотом есть, конечно, но это деталь реализации, а наружу ссылки, а не указатели.
Естественно, бизнес-задача же изменилась.
Бизнес-задача не изменилась, бизнес же о них не знает, в бизнес-логике их нет, требований к ним нет. :)
На уровне базы есть. ORM о нём знает и активно использует, в том числе для IdentityMap.
Нет, подождите. У нас приходит с фронтенда какая-то информация о параметрах договора "Создатель" и "Подписант", но не id. Что это за информация, которая однозначно их идентифицирует, позволяет загрузить из базы в IdentityMap, и применить к ним изменения из формы?
Это не единственный способ идентификации.
Это естественный для объектов способ идентификации. Можно сделать другие, но зачем? Первичный ключ для того и предназначен, чтобы идентифицировать объект в разных системах, независимо от положения на диске, в оперативной памяти, или еще где-то.
Зачем вышележащим слоям знать о первичном, когда задача идентификации объектов решается вторичным на бизнес-уровне?
Если он однозначно идентифицирует объект, то незачем, но и разницы с числовым id никакой нет. Это точно так же логический указатель, адрес в логическом пространстве.
а наружу ссылки, а не указатели
Я подразумевал их как синонимы.
Бизнес-задача не изменилась, бизнес же о них не знает
Как это не знает, если "раз и появляется требование сделать его строковым да в чётком формате"? Это ведь требование бизнеса.
У нас приходит с фронтенда какая-то информация о параметрах договора "Создатель" и "Подписант", но не id.
Скорее всего в данном случае это будут сущности и у них будет идентификатор, может сурогатный технический, может естественный для предметной области типа ИНН или табельного номера.
Это естественный для объектов способ идентификации.
Ничего естественного в нём нет для объектов.
Первичный ключ для того и предназначен, чтобы идентифицировать объект в разных системах, независимо от положения на диске, в оперативной памяти, или еще где-то.
Полностью не согласен. Первичный ключ — термин исключительно РСУБД (может других СУБД, но мы не о них) для идентификации кортежа в реляции.
Как это не знает, если "раз и появляется требование сделать его строковым да в чётком формате"? Это ведь требование бизнеса.
Это может быть требование DBA или фронтендеров. :)
может сурогатный технический, может естественный для предметной области типа ИНН или табельного номера
Ну так любой из этих вариантов и есть платформенно-независимый идентификатор. ИНН или табельный номер вообще ничем от числового id из базы не отличаются, это и есть придуманные суррогатные ключи чтобы различать объекты.
Ничего естественного в нём нет для объектов.
Объекты отличаются от значений только наличием первичного ключа.
Первичный ключ — термин исключительно РСУБД
РСУБД основаны на математике, первичный ключ это свойство данных, а не деталь реализации СУБД, он есть в теории баз данных.
Это может быть требование DBA или фронтендеров
Ну в данном контексте это несущественно, они выступают в роли заказчика. Но пусть так. Как вы поменяете для фронтендеров формат ключа на строковый в определенном формате, если в базе он числовой, в вашем варианте?
ИНН или табельный номер вообще ничем от числового id из базы не отличаются, это и есть придуманные суррогатные ключи чтобы различать объекты.
Отличаются. Как минимум ИНН для подавляющего большинства приложений — это внешний ключ.
РСУБД основаны на математике, первичный ключ это свойство данных, а не деталь реализации СУБД, он есть в теории баз данных.
Это не свойство данных, это свойство реляционной модели данных. Если в приложении, в бизнес-логике, в UI у нас не реляционная модель, то зачем в него тащить свойства реляционной модели?
Как вы поменяете для фронтендеров формат ключа на строковый в определенном формате, если в базе он числовой, в вашем варианте?
Для фронтендеров я просто добавлю новый ключ в нужном им формате, а на уровне СУБД+ORM буду решать добавить вторичный ключ куда-то или переделывать первичный, а может добавить новый как первичный, а старый первичный сделать вторичным. И решения о ключах дальше ORM не вылезут.
Как минимум ИНН для подавляющего большинства приложений — это внешний ключ.
Ну и? Идентификация объектов от этого никак не меняется, будете ли вы использовать числовой id в качестве ключа или числовой ИНН. Люди придумали ИНН ровно по тем же причинам и для тех же целей, для которых делают суррогатный числовой id для других сущностей.
Это не свойство данных, это свойство реляционной модели данных.
Нет. Это свойство вытекает из самой сути объекта — у него есть состояние и поведение, значит должна быть неизменяемая часть, чтобы в разные моменты времени можно было определить, что объект "тот же самый", что это не новый объект, а ранее известный объект изменил состояние. Везде, где есть объекты, вам будет нужен такой неизменяемый идентификатор. К реляционной модели принадлежит только сам термин "первичный ключ", в других областях он может называться "идентификатор" или "адрес". И индекс в коллекции это такой же идентификатор, просто более неустойчивый к изменениям. Если id это деталь реализации, то и индекс в коллекции это деталь реализации. Почему одно лучше другого?
Для фронтендеров я просто добавлю новый ключ в нужном им формате
И решения о ключах дальше ORM не вылезут.
Ну как это не вылезут?) Вы добавите столбец в базу, где будут храниться значения в этом формате, и будете передавать его через все слои, чтобы он дошел до фронтендеров. Никакой разницы.
Нет. Это свойство вытекает из самой сути объекта — у него есть состояние и поведение, значит должна быть неизменяемая часть, чтобы в разные моменты времени можно было определить, что объект "тот же самый", что это не новый объект, а ранее известный объект изменил состояние.
Вот когда по задаче надо определять тот же самый объект или нет, тогда и должен быть выденный идентификатор. Зачем он нам, если не надо определять или надо, но вполне хватает средств языка?
Ну как это не вылезут?) Вы добавите столбец в базу, где будут храниться значения в этом формате, и будете передавать его через все слои, чтобы он дошел до фронтендеров. Никакой разницы.
Я о ситуации, когда я уже по вашему совету на всякий случай пробросил числовой первичный ключ на фронт, а тут они просят сделать им текстовый идентификатор.
Зачем он нам, если не надо определять или надо, но вполне хватает средств языка?
Если не надо определять, у вас нету объекта, просто данные. Про средства языка я и говорю в том числе — указатель/ссылка это такой же идентификатор. Деталь реализации это не наличие id, это то, что id в базе данных это такой же атрибут, как и остальные. Почему я и сказал выше, что чтобы это убрать, надо менять работу всех инструментов, которые с ним взаимодействуют.
Просто когда у вас больше одного приложения, соответственно и идентификатор нужен более независимый. Вот например тот же список с автодополнением, для меня нет никакой разницы между ним и обычным, я даже не думал что это может что-то изменить, а вы сказали, что я требования усложняю. Раз у вас при таком небольшом изменении надо много переделывать, значит архитектура сделана слишком хрупкой.
Я о ситуации, когда я уже по вашему совету на всякий случай пробросил числовой первичный ключ на фронт, а тут они просят сделать им текстовый идентификатор.
Так вы же привели это как возможную проблему, мол, надо будет на всех слоях менять. У вас тоже надо будет на всех слоях менять, значит у вашего варианта преимуществ нет.
Если не надо определять, у вас нету объекта, просто данные.
Откуда эта мысль? Где в принципах или практиках ООП говорится о необходимости идентичности вообще и именно такой в частности?
Про средства языка я и говорю в том числе — указатель/ссылка это такой же идентификатор.
Нет. Это использование детали реализации языка в качестве средства идентификации объектов. Вот в PHP два способа сравнения: ==
и ===
, по значению и по ссылке. Выбираем нужный по задаче. А гипотетически можно представить что нет ни того, ни другого. Тогда вводить какой-то идентификатор нужно только если нам реально нужна операция проверки на идентичность. Вы же не будете писать свою обёртку над стандартным \DateTime, чтобы добавить зачем-то идентификатор.
Так вы же привели это как возможную проблему, мол, надо будет на всех слоях менять. У вас тоже надо будет на всех слоях менять, значит у вашего варианта преимуществ нет.
Добавлять и менять немного разные вещи.
Где в принципах или практиках ООП говорится о необходимости идентичности вообще и именно такой в частности?
Так я же написал — это следствие того, что у объекта есть состояние и поведение. Это более глобальное свойство, оно относится не только к ООП, а ко всем областям, где есть понятие "объект".
Объект (программирование)
"Объект в программировании — некоторая сущность в цифровом пространстве, обладающая определённым состоянием и поведением. Термин объект в программном обеспечении впервые был введен в языке Simula и применялся для моделирования реальности"
Объект (философия)
"Объект — философская категория, обозначающая вещь, явление или процесс, на которые направлена предметно-практическая, управляющая и познавательная деятельность субъекта (наблюдателя)"
Выделенные слова обозначают процессы, протяженные во времени, из чего следует, что в разные моменты времени необходимо определять идентичность наблюдаемых объектов. Если факты, полученные в результате познавательной деятельности, относятся к разным объектам, или неизвестно к разным или нет, то и каких-то выводов о взаимосвязях между фактами делать нельзя.
Вот в PHP два способа сравнения: == и ===, по значению и по ссылке. Выбираем нужный по задаче.
Это не средства сравнения объектов. По значению сравниваются значения. Объект состоит из 2 значений — идентификатор и состояние. В глобальном смысле идентификатор и ссылка это синонимы. Сравнение объектов по ссылке означает сравнение ссылок по значению. По задаче мы можем выбирать только нужны ли нам объекты с поведением или достаточно значений.
Вы же не будете писать свою обёртку над стандартным \DateTime, чтобы добавить зачем-то идентификатор.
Так он там уже есть, зачем его писать? Это ссылка, которая по факту адрес в оперативной памяти. Если нам нужен мутабельный DateTime с поведением, будем сравнивать по ссылке. Если значение, по значению. Если нам нужно хранить их вне запущенного процесса, например в БД, то да, там будет более глобальный идентификатор. "Мутабельный DateTime с поведением" это сущность "Событие".
Добавлять и менять немного разные вещи.
Ну а завтра фронтендеры попросят другой формат ключа, и надо будет уже именно менять. Смысл разговора же был в том, сколько слоев знают про этот идентификатор, а не про количество работы по рефакторингу.
Так я же написал — это следствие того, что у объекта есть состояние и поведение. Это более глобальное свойство, оно относится не только к ООП, а ко всем областям, где есть понятие "объект".
Я не понимаю по каким законам логики из наличия состояния и поведения вытекает необходимость идентификатора.
Объект состоит из 2 значений — идентификатор и состояние.
Это по вашему определению объекта? Вот я как объект, имею состояние, поведение, но я точно не имею в своём составе идентификатора.
Если нам нужно хранить их вне запущенного процесса, например в БД, то да, там будет более глобальный идентификатор. "Мутабельный DateTime с поведением" это сущность "Событие".
Это не событие обычно. DateTime — свойство события, но не каждый объект со свойством DateTime — событие. Человек вот имеет кучу связанных с ним дат, но человек — это не событие.
Я не понимаю по каким законам логики из наличия состояния и поведения вытекает необходимость идентификатора.
Без него вы не сможете их иметь, не сможете определить, что 2 состояния, полученные в разные моменты времени, относятся к одному и тому же объекту или к разным (отличить объекты друг от друга), не сможете управлять поведением, чтобы привести объект в нужное состояние. Вот как в примере с автокомплитом, надо задать подписанта договора, а на сервер пришел индекс 2. И что с ним серверу делать?
Вот я как объект, имею состояние, поведение, но я точно не имею в своём составе идентификатора.
Так это и не свойство объекта, это свойство системы, которая работает с объектом. Они ей нужны, чтобы различать объекты между собой, управлять ими. Почему я и сказал, что не совсем правильно иметь id в составе объекта как обычный атрибут.
Вот наблюдаете вы соседа как объект, у вас как у информационной системы с ним связан некоторый идентификатор, закодированный в работе нейронов. Благодаря ему вы можете отслеживать состояние в разные моменты времени и по этим наблюдениям определять его поведение. И даже прогнозировать в некоторых пределах, хотя эти события еще не произошли в реальности.
но не каждый объект со свойством DateTime — событие.
Естественно, только мутабельный с поведением. Который надо определять как "тот же самый" в разные моменты времени независимо от состояния и равенства этого состояния с состоянием другого объекта. Объект "Празднование дня рождения компании в 2019 году" у каждой компании свой, даже если они приходятся на одну дату. И перенести их можно на другую дату независимо друг от друга.
Без него вы не сможете их иметь, не сможете определить, что 2 состояния, полученные в разные моменты времени, относятся к одному и тому же объекту или к разным (отличить объекты друг от друга)
А зачем? Вот в общем случае, без привязки к конкретным задачам, где это реально нужно? Как по мне, то в бизнес-логике это относится только к объектам-сущностям. Даже событиям идентификатор не нужен с точки зрения бизнес-логики. Что произошло( создан продукт), когда произошло и, если нужно, детали, тот же идентификатор продукта, потому что продукт — это сущность.
Я же привел пример — задание подписанта, ему нужен id, более глобальный, чем адрес или индекс, чтобы его можно было указать в договоре, отправив это значение на сервер.
Как по мне, то в бизнес-логике это относится только к объектам-сущностям
Так я про них и говорю. Любые значения сравниваются по значению, и объектами как таковыми не являются. У них и поведения нет, любое изменение означает, что это другое значение.
События я имел в виду с точки зрения бизнеса. Как в Гугл-календаре, там это основная сущность, по дате вы их в пределах такого приложения не сможете различать, им нужен id.
Ну вот это конкретная задача связки двух сущностей (хотя бывают подписанты и не сущности на уровне модели). Сущносятм идентификатор нужен так скаазть по определению. Я же говорю про объекты вообще, а тот же DateTime или аналоги между прочим, часто мутабельный, с поведением.
Ну и с точки зрения бизнеса емогут быть события, которым идентификатор не нужен. Банальное наступление новых суток, недели, месяца, года… Важен только факт, что наступил новый период.
А, понял. С моей точки зрения DateTime это не "объект вообще", и вообще не объект, это значение. То, что он в PHP создается через new и обрабатывается системой как объект, это особенность PHP. В C++ можно и int через new создать, он от этого объектом не станет. Для меня объекты это всегда сущности.
Событиям наступления нового периода идентификатор не нужен только пока у вас обработка события гарантированно меньше, чем время до следующего, и соответственно событие всегда одно, и сравнивать не с чем. Если больше, то нужен, идентификатором может являться сама дата. Это хорошо заметно, когда делают пакетную отправку событий с фронта на бэк — вводится id, по которому можно понять, на какой именно запрос пришла ошибка.
Но это не совсем тот случай, про который я говорил, я имел в виду когда зачем-то может понадобиться сравнивать DateTime по ссылке, с возможностью менять саму дату.
Для меня объекты это всегда сущности.
Тогда для вас вы правы :) Для меня же объекты — это то, что создаётся в таких языках как PHP или JS с помощью new или аналогов. Если в C++ (new int(7)) !== 7 или типа того, как в JavaScript (new Number(7) !== 7), то слева тоже объект.
Событиям наступления нового периода идентификатор не нужен только пока у вас обработка события гарантированно меньше, чем время до следующего, и соответственно событие всегда одно, и сравнивать не с чем.
Обработка вполне может быть отложенной, просто откуда-то идёт поток событий и каждое обрабатывается по очереди. А идентификаторы вводятся для технических целей типа выявления повторно отправленных событий или пропущенных. Бизнес о них ничего не знает.
Это величайшее заблуждение что бизнес что-то решает, насажденное кажись фаулером.
Бизнесу нужен выбор сотрудника, ему все равно будете вы передавать Id или ссылку на объект. Пользователь который будет тыкать в вашу форму не заметит разницы какой у вас ORM и есть ли он вообще, правильно ли у вас смоделирована предметная область, используете вы MVC или MVVM.
Все ограничения типа "это должно быть в этом классе\слое\модуле, а это не должно" придумывают (внезапно) сами программисты.
Конечных пользователей интересует:
1) скорость создания и выкатывания фич
2) скорость и безошибочность работы
3) скорость исправления багов
Именно в таком порядке
Если вы можете на практике доказать что "числовой id на всех слоях" ухудшает все три параметра, то я с вами соглашусь. А пока выглядит так, что вы пытаетесь улучшить ситуацию в будущем, ухудшив её сейчас. Обычно это приводит только к ухудшениям в будущем.
1) не нужно маппить id, не нужно его пробрасывать через все слои, писать на него валидаторы и схемы
2) меньше данных гуляет между слоями — меньше вероятность ошибки, больше скорость. Это в общем, конкретно по ид — меньше ошибок из-за отсутствия конфликтов на разных слоях между ид и другими полями.
3) то же
В целом же по принципу "не множь сущности сверх необходимого" это сторонники пробрасывания ненужного ид через все слои должны доказывать необходимость этого :)
1) Мапинг обычно ничего. Просто в класс добавляется int Id
при этом точно будет id доступен там где нужно.
2) Передача между слоями делается копированием ссылок на объекты, а не копированием самих объектов, поэтому наличие id не влияет.
3) влияние наличия дополнительного свойства на количество ошибок как минимум не доказано
1) Обычно нужно указать в классе:
- что это свойство вообще персистится в базе
- (опционально) на какой столбец таблицы
- что это не рядовое свойство, а идентификатор
- часто указать, что это генерируемое где-то внутри свойство
2) под слоями я имею в виду не только и не столько слои, например, бэка внутри одного процесса, а передачу между разными процессами. Да и внутри одного процесса не всё так просто. IdentityMap может не быть d ORM и ко.
3) Теорию надежности пересказать своими словами?
меньше данных гуляет между слоями — меньше вероятность ошибки
Так это у вас больше вероятность ошибки, если вы завязываетесь на порядок в коллекции. Надо каждому разработчику знать, что вот эту коллекцию нельзя сортировать по-другому, добавлять или удалять элементы, и контролировать что нигде на N-ом уровне вложенности в вызовах функций она не меняется. С id нет никаких ошибок, он всегда однозначно идентифицирует объект в любом слое, независимо от того, в массиве он находится или отдельно.
Только один пример ошибок: два объекта с одинаковым id, но разными другими полями. Надо каждому разработчику знать, что вот эти объекты надо постоянно синхронизировать или вообще допускать в системе только один инстанс объекта
Если у вас откуда-то они взялись, значит что-то не так в логике обработки. При правильной логике такой ситуации быть не должно, потому что в реальных процессах она не возникает. Синхронизировать их точно не надо, один инстанс при правильном подходе получается автоматически, так как в дальнейшем просто передается внутри приложения. В разных запущенных экземплярах приложения такая ситуация возникает в обоих вариантах, и соответственно решается средствами более высокого уровня — транзакции, блокировки на редактирование, и т.п.
Для случая, когда редактируются 3 объекта сразу, как в вашем примере, вы сами писали "то не должен удивляться, что в базу запишется только последнее значение на момент вызова save или flush".
Ну и пусть. Если с «не совсем orm» проще писать программы чем с «совсем orm», то я лучше буду использовать первое.
С первой как раз часто SQL справляется далеко не отлично. Как по мне, то именно это основная причина появления универсальных ORM — обработка единичных изменений единичных объектов, а ещё точнее — относительно небольших графов объектов.
Разве отчётецы не делаются в специальных инструментах построения отчётецов, которые не знаю о ваших объектах?
А если ORM использовать только для CUD, а любой пользовательский R делать без ORM (или с другой ORM, менее мощной), то приходим к CQRS :)
А вот описать как проявляется проблема просто — в наличии мы имеем явную регулярность в данных которую на поверку невозможно повторить в UI на CRUD формах и списках — окна/страницы получаются нерегулярными. Т.е. невозможно написать достаточно гибкий кодогенератор который не утонул бы в сложности. Но причина возможно и не в сложности, а вполне банальное устаревание технологий, текучесть кадров — сложно довести до конца, возможно что и никому не нужно. К тому же пытаются генерировать код в архитектуру которая приспособлена для «копипасты» а не собственно кодогенерации (напр. код выдаваемый кодогенератором зачем-то пытается использовать биндинги, automapper вместо того чтобы просто прямо HttpRequest парсить и в оутпут что нужно стримить).
Есть NoSQL, базирующийся на понятии схемы и представляющий собой ни что иное, как иерархически организованную коллекцию объектов, строго следующих схемам. При этом, в отличие от мира РСУБД, где каждый воротит кто во что горазд, здесь схемы — в общем случае стандартизованы или как минимум ориентированы на совместное полезное применение множеством разработчиков.
Этот чудесный NoSQL со схемами существует минимум 24 года, описан набором стандартов RFC, и это — LDAP-каталог.
На данный момент наиболее популярны реализации: 389 DS, OpenLDAP, ReOpenLDAP, OpenDJ, Active Directory.
LDAP — это в чистом виде хранилище объектов (в форме атрибутов), это схемы, регламентирующие типы и названия атрибутов (а также дающие возможность одному атрибуту быть представленным сразу на нескольких человеческих языках), и конечно это S-выражения в основе языка запросов.
Что касается схемы, то она есть всегда. Даже если база бессхемная. Только она не в метаданных в СУБД записывается, а где-то ещё. Хоть в коде программы, хоть на прибитой к стенке бумажке, хоть в голове у сотрудника.
И самое интересное, что элементы этой схемы абсолютно в том же самом виде, с теми же названиями и типами атрибутов, хранятся и в тысячах других каталогов, препятствуя тому, чтобы кто-то называл поле с номером мобильного телефона вот так, а другой — разэдак. Т.е. базовые, фундаментальные схемы жёстко стандартизованы, благодаря чему одно и то же приложение может обращаться к совершенно разным «базам» — и получать полезный результат без всякого чудесного ORM-мапинга!
Строго говоря, и кастомные схемы можно и нужно стандартизовать, но если в этом нет строгой необходимости — можно этого и не делать. Тем не менее нет ничего проще, чем «расшарить» свою схему между неограниченным количеством пользователей любых LDAP-каталогов по всему миру — и позволить им, например, без проблем пользоваться предоставляемой вашей компанией инфраструктурой.
набор архитектурных принципов, позволяющих замэппить объекты на базу данных без использования ORM
Факты проецируются в объекты и обратно через проблемно-ориентированный API
Вау. И в результате получили ORM.
В свое время сильно помогли идеи, изложенные здесь, но будьте осторожны, там все немного радикально, куча интерфейсов, надо подходить чуть менее категорично.
Мои объекты напрямую не отражают структуру БД, а просто генерируют запросы через инкапсулированный объект подключения к БД (бывает, пишу SQL, бывает использую query builder).
А как результат запроса выглядит? Двумерный прямоугольный массив?
Так репозитории ORM или их аналоги типа статических методов класса AR и есть генераторы запросов.
А с отображением колонок на свойств проблемы таки есть. Без протечки абстракций, без того, чтобы любой посмотрев на код классов не сказал "100% состояние инстансов этого класса хранится в РСУБД" задача отображения графа объектов на схему РСУБД если и решается, то очень и очень непросто. Для банального наследования есть минимум три типовых решения, каждое со своими плюсами и минусами.
То что ORM запросы генерирует это само собой. Очевидно, без этого не обойтись. Но ORM это не только генератор запросов, если бы ORM была только генератором запросов, проблем бы не было… Но там есть еще куча всего, а в первую очередь это идея — построить в приложении абстрактное хранилище для объектных структур данных, под которое потом можно подвести реализацию, использующую СУБД. На мой взгляд это тот overkill из-за которого и появляются проблемы. Убрав эту идею, отталкиваясь от БД при работе с данными, мне удается избежать большинства возникающих проблем. С СУБД такое приложение будет работать как клиент, что более естественно, чем подводить базу к приложению как инфраструктуру по сохранению состояния независимых объектных структур.
Это отображение обычно двустороннее. Если нам надо две таблицы отобразить на коллекцию графов из объектов 5 классов, то чем это проще отображения этой коллекции на эти же две таблицы? Ну, при условии, что колонки PK однозначно отображаются на какие-то свойства объектов и обратно.
И я не согласен, что ORM содержит идею построить построить в приложении абстрактное хранилище для объектных структур данных, под которое потом можно подвести реализацию, использующую СУБД. Наличие универсальной ORM позволяет эту идею реализовать относительно малой кровью и с некоторыми ограничениями, но сама идея с ORM мало связана, ORM эту идею не реализуют обычно. Уж поверьте человеку, который репозитории ORM старается обернуть в свои обёртки, у которых ORM лишь внутренняя зависимость, и у клиентов которых доступа к ORM нет.
И я не согласен, что ORM содержит идею построить построить в приложении абстрактное хранилище для объектных структур данных
Ладно, скажу так: как только ORM перестает быть шлюзом к БД и начинает быть абстрактным хранилищем объектов — начинается overkill, который не нужен.
ORM обычно не становится абстрактным хранилищем объектов, иногда (не так часто как хотелось бы) на базе ORM реализуют такие хранилища.
Вам это не нужно, а я его делаю по умолчанию, когда решаю использовать domen driven development, а не data driven. Слишком часто сталкивался с ситуацией "мы бы рады перейти с MySQL на Postgre/MS/Oracle, а то и Mongo или Redis, но это всё надо переписывать — ресурсов нет."
Во-вторых, извините, но зачем программа обязательно должна моделировать мир?
Затем чтобы то, что происходит в программе, соответствовало тому, что происходит в мире. Если она так не делает, значит не соответствует требованиям, а значит бесполезна.
Мне кажется, весьма глубоко укоренившееся заблуждение насчёт моделирования возникло от того, что компьютер действительно является очень удобным инструментом для численного моделирования. Сначала в основном для этого компьютеры и использовались. Но сейчас времена уже давно как поменялись. Сейчас решение задач собственно моделирования занимает от силы 2% мировых вычислительных мощностей. Очень достойные и очень уважаемые 2%, но всё же 2%, если не меньше.
По-моему, вы слишком узко понимаете слово "моделирование", именно как численное, математическое моделирование. Бортовой компьютер беспилотного автомобиля или самолёта именно моделирует решение задачи по перевозке пассажира или груза, после чего отдаёт команды исполнительным устройствам автомобиля согласно модели, как правило принимая от датчиков результаты и корректируя модель в соответствии с реальным миром.
Молоток не моделирует забивание гвоздя. Так почему программа должна моделировать ту область, в которой она применяется?
Реальным молотком вы забиваете реальный гвоздь, они могут взаимодействовать друг с другом. Программа это информационная сущность, она не может взаимодействовать с физическими объектами, для правильной работы она должна иметь информационные модели физических объектов, и взаимодействовать с ними. Молоток не моделирует забивание гвоздя, но имеет характеристики и форму, способствующую забиванию гвоздя. Шуруповерт например имеет другую форму.
Сейчас решение задач собственно моделирования занимает от силы 2% мировых вычислительных мощностей.
Любая задача, где поведение реальных объектов заменяется на поведение информационных, либо поведение информационных объектов внешней системы на поведение внутренних информационных объектов, это моделирование. То есть практически 100% программ.
Информационные системы — это моделирование. Модель это нечто, что отвечает на вопросе о некотором аспекте реального мира вместо реального мира. Информационная система должна отвечать на вопрос "какое имя абонента" вместо того чтобы каждый раз спрашивать у абонента. Именно поэтому как только мы немножко пошевелил ваш пример про телефонную книжку, чтобы сделать его более реальным оттуда вылез абонент. Неважно будет это отношение или класс — человеку удобно думать в в этих терминах. Просто у каждой среды моделирования есть свои ограничения.
Меня смущает логический переход "если то что происходит в программе не соответствует тому что происходит в мире, то программа не соответствует требованиям".
По-моему соответствие требованиям никак не связно с моделированием мира.
Требования находятся вне программы, во внешнем по отношению к ней мире. Программа должна их моделировать, то есть моделировать часть внешнего мира. Поведение переменных должно соответствовать поведению, которое задается в требованиях.
Так программа должна требования моделировать или внешний мир? Требования ведь могут быть сформулированы не только в терминах физических объектов, но просто записей в БД.
Более того, аналитик всегда может требования переформулировать именно в терминах записей и экранов.
Внешний мир это не только физические объекты, это все что находится вне программы. Требования по поведению строк в БД это тоже внешние требования, и то что фактически происходит с этими строками в программе, должно им соответствовать.
Это если считать БД частью программы. Можно спуститься на уровень ниже и рассматривать их как отдельные системы, тогда в одной системе для правильного взаимодействия с другой системой тоже должна быть правильная модель. Это например драйвер БД, который понимает протокол. В ответ на этот запрос пришло 1 — значит строка правильно записалась. Для другого запроса 1 может означать ошибку, и надо выбросить исключение. Снова то, что происходит в одной системе, отражает то, что происходит в другой системе.
Я тоже раньше думал о том, что в ООП нужно ставить в соответствие программные объекты и объекты реального мира, потом понял, что заблуждался. ООП — это просто способ структурирования программы. Правила такого структурирования для общего случая тоже чисто программные, к реальному миру отношения не имеющие (GRASP, SOLID, low coupling, high cohesion). Да иногда удобно некоторые обекты (как правило для бизнес-логики) именовать и наделять поведением в соответствии с какой-либо сущностью реального мира, но это скорее частный случай. В любой ООП программе полно объектов, которые не имеют никакой связи с реальным миром, всякие фабрики, декораторы, прокси, провайдеры, подключения, мапперы и проч.
Программа не должна моделировать требования. Программа должна соответствовать требованиям заказчика
Моделировать это и есть соответствовать. Нельзя соответствовать требованию "При заказе на сумму больше N назначать его на старшего менеджера", не имея такого поведения в программе. То есть в программе будут сущности, которые некоторым образом обозначают заказ, сумму, менеджера, и их взаимодействие. Это и есть модель. Просто если язык поддерживает объекты, можно ими и моделировать, а не писать в процедурном стиле, раскидывая нужные характеристики по разным переменным и обозначая их связь в документации или комментариях.
Требования разработчиков это тоже внешние требования, разработчики ведь не сами байты на диск пишут. Для них в программе тоже должна быть соответствующая модель.
В любой ООП программе полно объектов, которые не имеют никакой связи с реальным миром
"Программа должна моделировать мир" не означает "В программе должны быть только объекты, моделирующие мир", означает "Объекты, моделирующие мир, там должны быть". Это обычно называют бизнес-логикой.
"Фабрики, декораторы, прокси, провайдеры, подключения, мапперы" это либо объекты, представляющие одну систему в другой (подключение к БД), либо объекты, выполняющие функцию, которая есть в требованиях (логирующий декоратор). Есть внешние по отношению к программе требования "Надо логировать вот эти действия", у тех кому это надо есть некоторые представления о том, какой должен быть результат, включая поведение во время выполнения действия. Вот программа и должна их моделировать, поведение объектов внутри нее должно соответствовать предполагаемому поведению объектов в требованиях. Объекты есть в требованиях изначально, потому что человеку удобно ими оперировать, в программе можно либо явно их задавать, либо косвенно через документацию или именование переменных и функций, если язык или платформа их не поддерживает. Например всякие функции с одинаковым префиксом "socket*", "curl*", куда первым параметром передается переменная определенного вида.
А кто вам сказал, что в программе не должно быть ничего кроме модели требований?
Так я же говорю, необязательно чтобы в программе были только и исключительно те объекты, которые моделируют физический мир или требования бизнеса. Объекты контроллеров и сервисов моделируют соответствующие объекты в представлениях разработчиков, которые появляются из наблюдения и обобщения различных методов обработки запросов к веб-серверу или размещения программной логики. "У нас есть админка, и есть методы 'Создать/Отобразить/Обновить/Удалить пользователя'. Удобно обработчики этих запросов объединить в одну группу. Назовем такие группы контроллерами.". Это не именно требования, сгруппировать можно и по-другому, но если программисты в своих представлениях решили группировать так, значит классы и объекты в программе должны этому соответствовать.
Архитектурных таких как "Логика диспетчеризации запросов, способа и формы ответа на них должны быть отделены от бизнес-логики"
Кроме заказчиков и программистов есть архитекторы, лиды и т. п.
P.S. Я честно говоря, вообще ни одного человека еще с должностью «Архитектор ПО» лично не встречал, как правило всё разработчики делают. Вот разделение на фронтенд/бэкенд часто вижу, но чтоб еще и «Архитектор»…
Требования разработчиков это тоже внешние требования, разработчики ведь не сами байты на диск пишут. Для них в программе тоже должна быть соответствующая модель.
Так речь о каких требованиях, требованиях закачика к ПО, которые в документах прописываются? Или о всем-всем-всем что все, в том числе разработчики хотят от программы?
Вообще любым. Для правильной работы программы важен любой байт кода, который в ней содержится. По чьим требованиям он появился, не имеет значения.
Заказчик определяет требования к программе и платит за их реализацию деньги. Разработчикам платят именно за реализацию требований заказчика, а не свои «причуды», которыми они могут наделить программу. Так что то, что внутри программы, ООП, ФП, процедурный код менее важно, чем реализация требований заказчика. У разработчиков может быть сколь угодно идеальная архитектура, паттерны и прочее. но если не реализованы требования заказчика, денег они не получат.
Так что то, что внутри программы, ООП, ФП, процедурный код
Это и есть модель.
но если не реализованы требования заказчика
О чем я сразу и сказал. Требования заказчика не касаются программы, они касаются его бизнеса, который происходит во внешнем по отношению к программе мире, даже если он связан с какими-то информационными сущностями. Да и сами информационные сущности внешние по отношению к программе. Если программа не будет эту часть внешнего мира правильно моделировать, она будет работать неправильно, не в соответствии с требованиями. Это и есть ответ на вопрос "зачем программа должна моделировать мир".
Моделировать можно по-разному (ООП, ФП, процедурный код, машинные команды), но если у нас в языке есть объекты, моделировать можно "один к одному", это и делают, когда выделяют сущности предметной области. "Один к одному" относится только к части программы (предметная область <-> бизнес-логика в коде), в других частях программы могут быть другие объекты и классы, которые моделируют требования, не связанные с собственно бизнесом. Но тоже являющиеся частью внешнего мира.
ORM: почему эта задача не имеет решения, но делать с этим, тем не менее, что-то нужно