Наверняка, каждый программист слышал о паттернах (шаблонах) проектирования, читал классику, и с той или иной периодичностью использует паттерны в повседневной работе для того, чтобы сделать дизайн своего приложения более понятным и масштабируемым. Если вы когда-нибудь пробовали использовать Linq to SQL, Entity Framework, NHibernate или другие ORM, то вы, конечно же, заметили, что у них есть много общего. L2S и EF с некоторого расстояния вообще выглядят, как близнецы-братья, да и остальные ORM тоже недалеко ушли. Вы думаете, это совпадение? Вовсе нет. Просто все они строятся с использованием одних и тех же паттернов проектирования.
Примечание. Хотел бы отметить, что использовать мы будем терминологию небезызвестного Мартина Фаулера, который является общепризнанным авторитетом в дизайне приложений, и его каталог "архитектурных" паттернов.
Начнем с того, что каждый ORM имеет в своем составе некий "менеджер" (например, контекст в L2S и EF, или сессию в NH), который является неким логическим фасадом (Facade) к базе данных. Этот фасад, как правило, "понимает" определенный маппинг объектов приложения на таблицы базы данных, "умеет" принимать на вход простые и сложные запросы на получение объектов, знает, "какие" изменения объектов и "как" сохранить в базе данных, и многое другое. Рассмотрим, какие паттерны работают внутри и рядом с ним.
Unit of Work
Unit of Work - наверно, самый популярный паттерн модификации данных. Он описывает следующую стратегию работы с данными:
- Получили объекты из ORM
- Изменили какое угодно их количество
- Сказали ORM сохранить данные в базу (или откатиться)
- ORM сохранил все изменения одним махом (или откатился)
Даже если вы никогда в жизни в глаза не видели ORM, то знакомо, не правда, ли? Конечно, именно по такому же принципу работает старая-добрая пара DataAdapter - DataSet в plain ADO.NET. И точно так же работают как контексты L2S и EF, так и сессия NH. Unit of Work работает благодаря тому, что он хранит в себе ссылки на все объекты, которые были созданы/получены через него. Именно поэтому без дополнительных телодвижений вам не удастся заставить контекст EF сохранить объект, который был изменен в рамках другого контекста, а потом приаттачен к текущему. Контекст просто не знает, какие изменения были сделаны в объекте, т.к. это не забота объекта "знать" о своих изменениях, а забота окружения, коим и является для объекта контекст/сессия.
Паттерн Unit of Work полезно использовать и в других случаях. Например, когда вам необходимо дать пользователю возможность изменить определенные данные приложения в памяти, а потом нажатием на одну кнопку сохранить их в базу данных. Или не сохранить.
Active Record
В противовес предыдущему паттерну, паттерн Active Record определяет, что объект сам знает, как себя извлечь из базы данных и сохранить изменения туда же. То есть объекту больше не нужны внешние "менеджеры", он сам в состоянии за себя постоять.
В целом, использование этого паттерна остается достаточно спорным, т.к. объект в таком случае "знает" о способе сохранения себя в базе, и поэтому крепко связан с ней, нарушая при этом Persistence Ignorance. Кроме того, если подобный объект содержит бизнес-логику, то он еще и нарушает Single Responsibility Principle. С другой стороны, у Active Record есть свои преимущества и сторонники. Из широко распространенных ORM паттерн ActiveRecord используется как минимум в Castle ActiveRecord (кто бы мог подумать :)). Да и Ruby-on-Rails, насколько я слышал, его уважает.
Identity Map
Identity Map - это паттерн, который дает возможность загрузить лишь один экземпляр объекта в память. То есть, если при первом обращении к определенному объекту ORM "поднимает" экземпляр этого объекта из базы, но все последующие запросы, которые будут приводить к получению этого объекта (даже если этот объект будет частью списка других объектов), будут всего лишь получать ссылку на уже созданный объект, а не его копию. Работает это благодаря тому, что внутри ядра ORM есть реализация этого паттерна. Контекст/сессия, перед тем, как материализовать (создать) объект с определенным Id, сначала просматривают это хранилище на предмет существования объекта, и если он не найден, регистрируют новый материализованный объект в хранилище.
Что это нам дает, как программистам. Прежде всего, если мы работаем с одним и тем же контекстом/сессией, то мы можем легко сравнивать объекты по ссылке (==). Кроме того, и что более важно, если мы уже начали изменять объект, а потом вдруг он появился в результате какой-то вспомогательной выборки, то мы можем быть уверены, что это один и тот же измененный объект (конечно, если вы напрямую не сказали при выборке, что вам необходим "чистый" объект). Ну, и напоследок, как side effect, мы получаем небольшое кеширование в рамках контекста/сессии, что позволяет уменьшить затраты на повторную материализацию объекта (весьма немаленькие). Естесственно, объекты, полученные из разных конекстов/сессий, не обладают подобными свойствами, что также необходимо понимать и учитывать.
Identity Field
Упомянув Identity Map, нельзя обойти стороной Identity Field. Этот небольшой паттерн, по сути, всего лишь говорит о том, что каждый объект содержит в себе уникальный ключ, который позволяет ORM правильно идентифицировать объекты у себя внутри. В частности, в EF такую функцию выполняет класс EntityKey, который состоит из названия сущности и значений первичного ключа.
Как вы уже догадались, Identity Field интенсивно используется для решения задач, описанных предыдущими паттернами.
Query Object
Паттерн Query Object реализует в себе запрос к данным. У Фаулера написано, что этот объект должен сам знать, как превратить себя в SQL-запрос, но очень часто паттерн рассматривается более широко, описывая прежде всего способ составления сложных запросов к ORM.
В NH есть механизм запросов Criteria, который можно отнести к реализации этого паттерна. В L2S и EF эту функцию выполняет язык запросов LINQ, который также можно назвать его реализацией (хотя и с большой натяжкой).
Lazy Load
Этот паттерн известен далеко за пределами работы с базой данных, т.к. его очень часто используют и в других областях. Lazy Load описывает объект, который может в определенный момент времени не содержать всех данных, которые нужны ему или другим объектам, но "знает", как и откуда эти данные получить. При этом после первого обращения и загрузки данных в объект, они кешируются в нем и уже следующие операции проходят быстрее.
Lazy Load в том или ином виде реализован практически во всех известных мне ORM для улучшения производительности. Ведь не всегда ваш объект Заказ должен загружать сразу и список Продуктов - они могут просто не понадобится в данной транзакции, так зачем тратить на это время и ресурсы?
Repository
Я не мог пройти мимо этого паттерна, хотя на самом деле он не реализован ни в L2S, ни в EF, ни в NH, ни в AR. (Возможно, реализован в других ORM, подскажите, если знаете.) Не мог пройти, потому что паттерн Repository очень часто используется при работе с NH (1 и 2), да и примеры для других ORM уже подоспели, например, для L2S (1 и 2).
С простой точки зрения, этот паттерн определяет фасад, который предназначен для того, чтобы быть промежуточным слоем между доменной моделью (бизнес-логикой) и источником данных. Весь код приложения за пределами репозитория работает с базой данных (или другим источником данных) через репозиторий. Однако, если смотреть на него с точки зрения пуристов DDD, то репозиторий - это уже нечто другое. Всех желающих погрузиться в нирвану DDD отправляю к следующему источнику, который достаточно неплохо описывает Repository с точки зрения DDD. Не заблудитесь только там :)
Заключение
Предлагаю пока что ограничится данными паттернами, т.к. они являются основными паттернами поведения ORM. Тех же, кто хочет узнать больше, отправляю к Фаулеру, в каталоге которого описаны еще с десяток других паттернов, связанных с другими аспектами работы Data Access в целом, и ORM в частности.
Надеюсь, данный пост помог вам немножко лучше понять, какие механизмы лежат в основе работы привычных инструментов, а также при необходимости суметь объяснить еще кому-нибудь, чем настолько принципиально отличается NHibernate и ActiveRecord, что потребовалось городить свой огород.
Занёс в рекомендуемые статьи. Спасибо, для меня бОльшую ценность оказало не столько содержимое статьи (всё это уже знаю), сколько ссылки разбросанные по тексту =)
ReplyDeleteРад, что нашли хоть что-то полезное для себя :) Надеюсь, кому-то будет полезен и остальной материал.
ReplyDeleteОн очень интересен, я даю его почитать новичкам. Респект короче =)
ReplyDeleteВ динамических языках Active Record - замечательная вещь. Persistance Ignorance в таких языках как Ruby может и не нарушаться этим паттерном. В зависимости от провайдера реализация AR меняется динамически. Например, в том же Ruby On Rails вообще не надо ни маппингов, ни сущностей писать - только новый класс объявляется автоматически (грубо говоря, его имя). После этого механизм AR динамически "подмешивает" все необходимые методы исходя из схемы базы данных, то есть DAL создается на лету. Время разработки сокращается в разы, а благодаря такой штуке как миграции, новые изменения вносить тоже легко и приятно.
ReplyDeleteС нетерпением жду динамического C#4.0. Думаю, когда и для него сделают аналогичный динамический AR , у ORM будет достойная альтернатива, особенно при RAD.
За все нужно платить :) Если в C# 4.0 появится автоматический AR (пока что слабо представляю себе, как это будет работать), то за это мы все заплатим еще более глубокими провисаниями в области производительности. Какой бы неудобной ни был маппинг и кодогенерация (или рефлексия) в современных ORM, это по крайней мере работает за адекватное время.
ReplyDeleteКроме того, наличие Active Record не решает многих других проблем. Например, Identity Map реально помогает в случае "поднимания" сущности в память из нескольких запросов к базе. Объект будет один. Как в этом случае будет работать AR? Да и вообще, как подобные задачи решаются в том же Ruby on Rails?
Я могу ошибаться но у ActiveRecord есть Identity Map. Да и по перфомансу все точно также. С чего бы это у ActiveRecord были провисания по преформансу?
ReplyDeleteПро какой Active Record ты говоришь? Про паттерн или про фреймворк? В фреймворке, возможно, и есть, потому что он базируется на NH, а в NH точно есть реализация Identity Map. Если мы говорим про паттерн, то все зависит от реализации. Думаю, ты без проблем можешь реализовать свой Active Record так, что при запросе того же объекта из базы в другом запросе, он вернет тебе ссылку на существующий. Но наверно, это будет дополнительная головная боль, не знаю...
ReplyDeleteА фраза о провисаниях в перформансе была для динамических AR-объектов, а не для паттерна в целом. Собственно, провисание будет заключаться как раз в том, что есть динамический "маппинг", о котором говорил Otozvalsa.
Я про Ruby On Rails Active Record. Хотя судя по всему там таки нема Identity Map.
ReplyDeleteПро провисания. Я технически не вижу проблем. С точки зрения реалзиации у тебя есть результат запроса ввиде колекции ключ/значение. Какая разница есть мапинг или нема? В мапинге итерируемся по мапингам и асайним в проперти. В динамическом, просто все ключ/значение асайним в проперти.
Про RoR вообще ничего сказать не могу :(
ReplyDeleteПо поводу провисаний - похоже, что ты прав :) Более того, если маппинга нет, то не нужно делать дополнительные проверки... Так что, возможно, динамические проперти даже быстрее работать будут.
Очень нравится ваш блог, уже не первую статью читаю. Полезно, спасибо
ReplyDeleteПо поводу реализации паттерна Repository с помощью LinqToSql, возможно вас заинтересует мой проект - lsda.codeplex.com. Там реализация Generic Repository под LinqToSql и LinqToObjects c поддержкой ассоциаций, шейпинга, интерсепторов и прочих плюшек.
ReplyDeleteгде DAO?
ReplyDeletehttps://bayanlarsitesi.com/
ReplyDeleteTokat
Kastamonu
Tekirdağ
Gümüşhane
RRFTX0
yalova
ReplyDeleteyozgat
elazığ
van
sakarya
BKTV
bitlis
ReplyDeletesakarya
van
tunceli
ankara
5VZFQ
çankırı evden eve nakliyat
ReplyDeletekırşehir evden eve nakliyat
kütahya evden eve nakliyat
hakkari evden eve nakliyat
antalya evden eve nakliyat
BSLK
kayseri evden eve nakliyat
ReplyDeleteantalya evden eve nakliyat
izmir evden eve nakliyat
nevşehir evden eve nakliyat
kayseri evden eve nakliyat
H5İF
kayseri evden eve nakliyat
ReplyDeleteantalya evden eve nakliyat
izmir evden eve nakliyat
nevşehir evden eve nakliyat
kayseri evden eve nakliyat
YWMR27
82DFB
ReplyDeleteAntep Lojistik
Manisa Evden Eve Nakliyat
Bitlis Parça Eşya Taşıma
Zonguldak Evden Eve Nakliyat
Isparta Parça Eşya Taşıma
08D12
ReplyDeleteYalova Evden Eve Nakliyat
Kars Lojistik
İstanbul Lojistik
Ordu Parça Eşya Taşıma
Kilis Parça Eşya Taşıma
E0319
ReplyDeleteDenizli Şehir İçi Nakliyat
Bitmart Güvenilir mi
Giresun Parça Eşya Taşıma
Paribu Güvenilir mi
Nevşehir Şehir İçi Nakliyat
Maraş Lojistik
İstanbul Parça Eşya Taşıma
Maraş Parça Eşya Taşıma
Adıyaman Şehir İçi Nakliyat
AB9A4
ReplyDeleteBitfinex Güvenilir mi
İzmir Şehirler Arası Nakliyat
Uşak Şehir İçi Nakliyat
Kırklareli Şehirler Arası Nakliyat
Karabük Lojistik
Edirne Parça Eşya Taşıma
Keçiören Boya Ustası
Mardin Şehir İçi Nakliyat
Tekirdağ Çatı Ustası
40664
ReplyDeleteÜnye Televizyon Tamircisi
Muğla Şehir İçi Nakliyat
Çerkezköy Oto Boya
Çerkezköy Kurtarıcı
Eskişehir Evden Eve Nakliyat
Bilecik Evden Eve Nakliyat
Kayseri Evden Eve Nakliyat
Kırşehir Parça Eşya Taşıma
Konya Evden Eve Nakliyat
5002A
ReplyDeleteCoinex Güvenilir mi
Maraş Şehirler Arası Nakliyat
Kastamonu Şehir İçi Nakliyat
Balıkesir Parça Eşya Taşıma
Erzurum Şehirler Arası Nakliyat
Artvin Parça Eşya Taşıma
Çerkezköy Halı Yıkama
Amasya Şehirler Arası Nakliyat
Gümüşhane Parça Eşya Taşıma
DFC48
ReplyDeleteBatıkent Fayans Ustası
Edirne Parça Eşya Taşıma
Bolu Lojistik
Samsun Şehir İçi Nakliyat
Çankaya Boya Ustası
Gölbaşı Parke Ustası
Elazığ Lojistik
Kalıcı Makyaj
Mardin Lojistik
74190
ReplyDeleteMamak Fayans Ustası
Zonguldak Şehirler Arası Nakliyat
Çankaya Fayans Ustası
Karabük Evden Eve Nakliyat
Hotbit Güvenilir mi
Kocaeli Şehir İçi Nakliyat
Çerkezköy Buzdolabı Tamircisi
Şırnak Şehir İçi Nakliyat
Artvin Lojistik
9CD56
ReplyDeleteniğde canlı görüntülü sohbet siteleri
uşak chat sohbet
bursa yabancı görüntülü sohbet
yalova tamamen ücretsiz sohbet siteleri
urfa sesli sohbet siteler
kütahya ücretsiz sohbet uygulamaları
görüntülü sohbet siteleri
gümüşhane en iyi görüntülü sohbet uygulaması
manisa görüntülü sohbet yabancı
BA992
ReplyDeletekocaeli random görüntülü sohbet
Kastamonu Muhabbet Sohbet
trabzon sohbet uygulamaları
aydın mobil sohbet bedava
aksaray sohbet uygulamaları
ığdır En İyi Sesli Sohbet Uygulamaları
parasız sohbet siteleri
Sinop Muhabbet Sohbet
istanbul canlı sohbet uygulamaları
14770D2878
ReplyDeletetelegram görüntülü şov
افضل شركة تسليك مجاري بالاحساء PY91Lbdwto
ReplyDeleteشركة مكافحة حشرات بابها xiiiupHaF4
ReplyDelete