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

Комментарии 50

Интересно! Давно хотел почитать какую-нибудь книгу именно о философии практик программирования, ведь в современных подходах просто до ужаса много общего, и всегда интересно знать мнение товарищей, чей опыт использования отличается от собственного. Надеюсь, в продолжении книги этот лейтмотив развивается, а не остается на уровне тривиальных примеров.
«Использовать сиквел напрямую теперь затруднительно»
Замечал, что некоторые люди говорят «сиквел», а некоторые «скуль».

А вообще приятное изложение, даже немного обидно, что отрывок закончился слишком внезапно. Когда книга выйдет?
Если всё будет по плану, то в марте.

Я могу выложить еще главу на следующей неделе.
Спасибо за информацию. Да, мне было бы интересно почитать ещё.
Да, выложите, пожалуйста, ещё главу. Очень интересно написано. И ещё, если можно, поделитесь полным содержанием книги, интересно какие ещё темы затронуты.
А будут ли в книге освещены ситуации, сталкиваясь с которыми разработчики предпочитают не использовать в каком-то конкретном проекте ORM? Конечно красиво написано в примере в книжке, как удобно доставать salary of employee in department, но в жизни разработчики сталкиваются с гораздо более сложными и мудреными задачами.
еще +1 за выкладывание
habrahabr.ru/company/piter/blog/165757/
Еще главка и содержание
404 там…
Этот пример можно написать и в более симпатичном виде.
var queues = from t in session.Query<TaskQueue>()
                         where someTask.Contains(t.Task.Id) && q.TaskOrigin.Id == 10
                         select t;

А попробуйте написать такой запрос на SQL в более понятном виде:
            var queues = from t in session.Query<TaskQueue>()
                         where someTask.Contains(t.Task.Id) 
                                && q.Client.Address.Country.Languages.Contains(englishLanguageId)    
                         select t;



У меня на php так:

$taskQueues = \TaskQueue\Model::select()
        ->withTaskId([1, 2, 3])
        ->withTaskOrigin(10)
        ->getCollection();


Второй ваш запрос я не понял, честно, но наверное, потому, что с этой ORM я не знаком.

Но если простой join, то примерно так:
$clientSelector = \Client\Model::select()
        ->withNameNotContains('Артур');

$taskQueues = \TaskQueue\Model::select()
        ->join($clientSelector)
        ->getCollection();


ну или так:
$taskQueues = \TaskQueue\Model::select()
        ->join(\Client\Model::select()->withNameNotContains('Артур'))
        ->getCollection();

Но предыдущий вариант, мне нравиться больше, если условий больше.
Во втором случае там не просто Join, а выборка задач клиентов, проживающих в стране, у которой среди государственных языков присутствует английский. Ну т.е. 4 join'а.

В этом, кстати, кроется проблема ORM — дырявая абстракция. Да, Linq2SQL/EF сам сгенерирует правильный запрос, и в некоторых случаях даже более оптимальный, чем написали бы некоторые программисты, но иногда люди забывают, что кроется за «Client.Address.Country.Languages.Contains», и что вероятнее всего есть более простой и оптимальный способ решения задачи.
Спасибо за информацию, попробую у себя как-то такое реализовать, думаю поможет разрабатывать быстрее.

Скажите, а IDE поддерживают автокомплит такой записи — «q.Client.Address.Country.Languages.Contains»? Я люблю записи в виде цепочки, но, если IDE мне помогает) Просто не знаю, в рамках какого языка это пример, но что-то похожее на C# или Java.
Разумеется, автокомплит поддерживается, как и проверка выражений при компиляции программы.
Фокус в том, что на каждую таблицу БД создаётся класс. Поля класса соответствуют столбцам таблицы.

Если в таблице Q есть поле ClientId, которое указывает на Id в таблице Clients, то в ORM-классе появляется ссылочное поле Client, при обращении к которому (например, в выражении q.Client.FIO) в поле q.Client прозрачно подгружается запись из таблицы Clients по нужному Id, и выражение q.Client.FIO — это уже ФИО клиента из таблицы клиентов. Естественно, есть кеш и любые обращения к уже загруженным записям читают из кеша.
Ага, уже понятнее. Вы меня остановите пожалуйста, если я тут флуд устраиваю. Ссылочное поле Client появляется при прекомпиляции или его заводит программист?
Есть три подхода к генерации ORM-классов:

1. Программист пишет классы полностью вручную, и поля с примитивными типами данных, и ссылочные. Затем настраивает — указывает, что класс Client сопоставлен с таблицей TBL_CLIENTS, а поле Client в классе Order соответствует записи в таблице TBL_CLIENTS, загружаемой по значению из столбца CLIENT_ID таблицы TBL_ORDERS.

Могут быть и обратные связи: например, коллекция Orders в классе Client собирается по ссылкам на эту запись в TBL_CLIENTS из поля CLIENT_ID таблицы TBL_ORDERS. То есть, в таблице TBL_CLIENTS нет никаких упоминаний о заказах, но в классе Client есть коллекция заказов данного клиента.

При желании по написанному программистом коду можно сгенерировать базу данных.

2. Программист рисует UML-модель (сущности и связи между ними), ORM по модели генерирует классы и БД

3. Программист натравливает ORM на существующую базу, она генерирует прокси-классы.

Некоторые ORM поддерживают все 3 подхода, некоторые — только 1-й или 3-й
Понятно, главное, что никакой магии с полем Client нет, это просто public свойство Q верно?
Да, за исключением того, что обычно все ORM-поля не обычные поля, а свойства.
Т.е. при чтении вызывается код getField при записи setField

ORM накладывает свои обработчики на эти методы (требуя, чтобы свойства были описаны как virtual, а клиенту при загрузке из базы отдавая не чистые классы, а наследников, где эти методы уже перегружены). Клиент не замечает, что это наследники и работает с загруженными классами как со своими. В обработчиках get{Field} ORM подгружает данные при первом обращении к ссылочному полю, а в set{Field} помечает у себя, что перед закрытием транзакции нужно сделать запись изменений в БД.

Технологически современный ORM не так прост, потому что подразумевает кодогенерацию классов-наследников.
>>>но иногда люди забывают, что кроется за «Client.Address.Country.Languages.Contains»

Нет ли такой проблемы в любом средстве высокого уровня?
Да, конечно, такая проблема есть всегда, когда поверх одной системы строится другая, чуть более упрощенная и призванная скрыть детали реализации первой. Закон дырявых абстракций, как назвал это Джоэль Спольски. Согласен, что это не имеет прямого отношения к концепции ORM. Просто хотел напомнить, что у любого инструмента, у ORM, у SQL, у использованного фреймворка, у использованных протоколов, у железа, и т.п. есть ограничения и узкие места, о которых нужно помнить, и что серебряной пули не может быть. Причем, чем сложнее сценарий использования, тем чаще натыкаешься на узкое место, поэтому постоянно приходится балансировать между полученными выигрышем по одним из критериев и проигрышем по другим.
В моей команде ORM не используется, у нас есть аналитики – эксперты в областе SQL и различных СУБД, которые проектируют БД и строят запросы. Эти запросы переносятся в код практически без изменений, и они могут его отлаживать, изменять в случае необходимости. С ORM это было бы затруднительно.
А как вы потом результаты запросов скрещиваете с объектами? Руками?
Результаты запросов интерпретируются как массивы данных, объекты тут больше не причем.
а как к полям обращаетесь в этих массивах? по индексу??
по названию например.
Т.е. слоя модели у вас в коде нет, и во всех других слоях Вы опрерируете просто полученными массивами?
1. Я не говорил про MVC.
2. У MVC есть множество модификаций, трактовок.
Как можно сравнивать ORM и SQL? ORM помимо самой выборки еще занимается отображением выбранных данных в классы, что избавляет нас от лишнего кода. Элегантный пример sql запроса не отобразит нам полученные данные в классы для последующей обработки в приложении.

Кроме того, существуют хранимые процедуры для написания сложных запросов в случаях, когда оптимальность запросов ORM ставится под вопрос.

На текущем проекте мы перешли на использование легковесного tiny orm PetaPoco — все очень быстро и хорошо.
Нормальная ORM вам отобразит данные, возвращенные элегантным примером sql-запроса.
> Кроме того, существуют хранимые процедуры для написания сложных запросов в случаях, когда оптимальность запросов ORM ставится под вопрос.

Главное с этим не переусердствовать и не наплодить хранимых процедур, а то ORM со своим кэшированием будет совсем уж не к месту (а без кэширования лучше юзать что-нибудь попроще).
PetaPoco это совсем micro ORM, представляет с собой один файл с открытым кодом. Сейчас готовлю статью на эту тему.
SQL
SELECT *
FROM task_queue
WHERE
  id_task IN (2, 3, 15) 
  AND id_task_origin = 10


JQL
SELECT tq
FROM taskQueue tq
WHERE
  tq.id_task IN (2, 3, 15) 
  AND tq.id_task_origin = 10


В случае SpringData:
public interface taskQueueDao extends CrudRepository<taskQueue, Long> {
   @Query("SELECT tq FROM taskQueue tq WHERE  tq.id_task IN (2, 3, 15)  AND tq.id_task_origin = 10")
  public List<taskQueue> getTaskQueue();
} 


JQL входит в стандарт JPA. Для оптимизации запроса можно крутить хинты и указывать где использовать жадную загрузку и join.

Все давно можно делать проще и через аннотации. И да это всяко лучше чем в ручную писать тот же самый ORM.

К тому же любой нормальный ORM позволяет составлять запросы руками.

Технически же определить хороший или плохой перед вами ORM можно всего по двум критериям

1. Генерация domain классов по базе.
2. Наличие возможности использования SQL в запросах при необходимости.

Я еще не видел ни одного плохого ORM который бы включал эту функциональность. Генерация же метаданных только в одну сторону повод задуматься, а все ли хорошо с этим ORM.
А я пользуюсь СУБД Caché, которая одновременно предоставляет и объектный, и реляционный, и прямой доступ.
Встроенная поддержка взаимного отображения, например классов в таблицы и таблиц в классы, позволяет в SQL использовать объектные расширения из любого ODBC/JDBC клиента.
Подробности можно почитать в документации.
мне показалось, что тема не раскрыта.
Очень и очень однобоко.

1) ORM и называется Mapper, потому что в первую очередь отображает данные из одного «пространства» в «другое» и предоставляет унифицированный интерфейс, а не генерирует запросы. С этой стороны их и надо рассматривать в первую очередь (как orm позволяет представлять модели, как работают миграции, во сколько обходится изменение модели итп итд).

2) Помимо проблемы эффективности запросов есть еще проблемы стоимости разработки, тестирования, поддержки, развертывания итп. А итоговое решение — это всегда компромисс между всеми этими процессами и требованиями к продукту. Нахрена мне использовать дорогой в поддержке и отладке SQL в системе в которой нет тяжелых и сложных выборок? Зачем за это платить заказчику?

3) Как уже писали выше и как завещал Фаулер, никто не запрещает оптимизировать узкие места с помощью хранимых процедур. Если эта оптимизация оправдана и не выливается в издержки, превышающие business value оптимизации.

Единественно с чем соглашусь с автором — действительно, для очень многих задач для которых хорошо работают ORM использование РСУБД в связке с ORM являет сильным оверхедом. Очень часто хватает NoSQL-хранилища и легковесного маппера вокруг него.
«Однако, запросы типа «выбрать сотрудников, зарплата которых в течение последнего года не превышала среднюю за предыдущий год» уже вызывают проблемы на уровне встроенного языка.»

Не вижу коренной разницы в том как будет написан linq-запрос и соответствующий ему sql. И как он будет сгенерирован вменяемой ORM.
Автор забыл написать юс-кейс, где мощь ORM раскрывается наиболее полно.
А именно, сложная логика с циклами, ветвлениями и обновлениями данных.
Например,
foreach (var ware in order.Wares) {
  if (ware.Price > 100 && order.Customer.City == CityCodes.Moscow) {
    order.BonusWares.Add(new BonusWare(order, WareCodes.SamsungTV);
    ware.Price *= 2;
  }
}

Страшно представить, как этот код выглядел бы на SQL, с его declare cursor и циклом open… while… fetch, или на голом c# с insert/update запросами средствами ADODB.Command. А если бизнес-правил в коде, типа вышеприведённого — 1.5 страницы, то переписать на голом SQL/ADODB, а потом это ещё и поддерживать — быстрее застрелиться.

Разумеется, запросы типа «выбрать сотрудников, зарплата которых в течение последнего года не превышала среднюю за предыдущий год» — не конёк ORM. Зачем автор в этом контексте вообще о них вспомнил.
Эм то что вы написали отлично делается на SQL и без курсоров.
Да, но это сложнее поддерживать.

И плохо контроллируется при усложнении задачи. Если внутри цикла не один if, а десяток (теоретически, тоже можно переписать без курсоров), то 20 строк, которые занимает 1 select, выглядеть будут очень страшно.
Да, но это сложнее поддерживать.

Зависит от задачи. Но большие объемы данных так лопатить можно только в случае реально сложной бизнес-логики. И часто бывает дешевле это вынести в хранимку и в ней сделать декларативную обработку. Любая процедурная обработка данных из СУБД делается существенно медленнее, чем декларативная в самой СУБД, давно известный факт.

И плохо контроллируется при усложнении задачи. Если внутри цикла не один if, а десяток (теоретически, тоже можно переписать без курсоров), то 20 строк, которые занимает 1 select, выглядеть будут очень страшно.

В этом случае можно написать хранимку. Ваш пример хорош если у вас данных немного и вы их обрабатываете реально сложным алгоритмом.
«Как связать мир SQL и ООП? Какой 'мост' для это использовать?» Эти вопросы я уже не раз задавал, и в том числе на хабре, но ответа не было. Поэтому задаю еще раз: Как обеспечить взаимодействие ортогональных технологий? Как построить гибкую архитектуру, что бы проект можно было адаптировать к новым требованиям? Как снизить энтропию в нем? Как защитить существующий код от изменений? Как обеспечить модульность проекту?
— Если использовать напрямую SQL запросы в коде, то получается, простите за выражение, говнокод. Если измениться структура данных — код проекта придется менять, и не просто менять, а менять в нескольких местах.
— Если используешь ORM, то до определенного момента все идет как по маслу. Геморрой начинается когда проект уже завязан на ORM, и оказывается что в ней не хватает какой-то функциональности. Тогда начинаешь просто волосы рвать на себе, за то что выбрал эту ORM, а не другую (у которой проблем ни чуть ни меньше). Да существуют очень крутые ORM, но как уже было написано, они не выразительны с их собственным «SQL» языком.
— Перенос части логики в БД по средствам хранимых процедур. Очень привлекательный метод. Однако как минимум появляется привязка к конкретной СУБД. Появляются трудности с отладкой. Обеспечить модульность проекта не очень просто. В одном из своих проектов я, ради эксперимента, превратил PostreSQL фактически в RPC сервер. Не могу сказать что результат мне понравился. Возможно я что-то не учел, или не правильно построил архитектуру.
— Использование NoSQL. Сам не юзал. Но люди говорят, что там своих проблем куча.
Какие есть еще походы? Какие есть еще соображения?
P.S. По профессии я не программист — сисадмин я. Программированием занимаюсь как говориться «не ради пьянства окаянного, а дабы не отвыкнуть», поэтому многого не знаю и хотел бы услышать мнение других.
1. Берешь myBatis
2. Пишешь SQL
3. Получаешь объекты
4.…
5. Profit!
В принципе не плохо. А кроме JAVA и .Net поддерживаются еще какие-нибудь языки? Или все остальные в пролете? Я использую C/C++/Python.
Не в курсе.

Беглый просмотр интернетов не позволил найти аналогов myBatis для C++. Но это был бы весьма перспективный опенсурс проект.
Автор не упомянул про myBatis и похожие технологии, которые могут быть полезны в определенных обстоятельствах. По сути, автор рассматривает 2 крайности и критикует одну из них.
Как-то мутно и непонятно. Может, кто-нибудь подскажет, на какой уровень читателей рассчитана эта книга? И что стоит почитать, чтобы достичь этого уровня?
Я так понимаю основной посыл — «ORM портит людей!» :)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий