Sunday, February 22, 2009

Немного о проектировании: паттерны из мира ORM

Наверняка, каждый программист слышал о паттернах (шаблонах) проектирования, читал классику, и с той или иной периодичностью использует паттерны в повседневной работе для того, чтобы сделать дизайн своего приложения более понятным и масштабируемым. Если вы когда-нибудь пробовали использовать Linq to SQL, Entity Framework, NHibernate или другие ORM, то вы, конечно же, заметили, что у них есть много общего. L2S и EF с некоторого расстояния вообще выглядят, как близнецы-братья, да и остальные ORM тоже недалеко ушли. Вы думаете, это совпадение? Вовсе нет. Просто все они строятся с использованием одних и тех же паттернов проектирования.

Примечание. Хотел бы отметить, что использовать мы будем терминологию небезызвестного Мартина Фаулера, который является общепризнанным авторитетом в дизайне приложений, и его каталог "архитектурных" паттернов.

Начнем с того, что каждый ORM имеет в своем составе некий "менеджер" (например, контекст в L2S и EF, или сессию в NH), который является неким логическим фасадом (Facade) к базе данных. Этот фасад, как правило, "понимает" определенный маппинг объектов приложения на таблицы базы данных, "умеет" принимать на вход простые и сложные запросы на получение объектов, знает, "какие" изменения объектов и "как" сохранить в базе данных, и многое другое. Рассмотрим, какие паттерны работают внутри и рядом с ним.

Unit of Work

Unit of Work - наверно, самый популярный паттерн модификации данных. Он описывает следующую стратегию работы с данными:

  1. Получили объекты из ORM
  2. Изменили какое угодно их количество
  3. Сказали ORM сохранить данные в базу (или откатиться)
  4. 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, что потребовалось городить свой огород.

Sunday, February 15, 2009

След Шерлока Холмса в программировании или Немного об agile для программистов

Задумывались ли вы, чем отличаются между собой waterfall и agile подходы к разработке программного обеспечения? Для меня, человека, который раньше работал либо по ad hoc, либо по waterfall, либо по итеративным процессам вроде UP, agile-подходы вроде XP, Scrum и др. (прошу не бить сильно ногами, что буду все причесывать под одну гребенку) раньше казались чем-то сродни ad hoc'а, только с дополнительными правилами, чтобы совершенно не скатиться в анархию. У нас даже шутка ходит о том, на проекте часто бывает не просто agile, а полный agile :) Однако после прочтения книжки Книберга я изменил свое мнение.

Итак, что же такое agile и как его проще всего понять программисту? Помните, что такое программирование сверху-вниз, а что такое - программирование снизу-вверх? При первом подходе мы сначала все продумываем, разрабатываем каркас, создаем классы верхних слоев, а затем уже спускаемся вниз, к деталям, наполняя их кодом и смыслом. Второй подход - прямая противоположность первому: мы сначала пишем код нижних слоев, потом интегрируем их друг с другом посредством более высоких уровней и, наконец, пишем вызывающую программу. По сути, если говорить математическим (или детективным ;)) языком, это дедукция и индукция.

Примечание. Кстати, лично мне кажется, что Шерлок Холмс пользовался все-таки индуктивным методом, а не дедуктивным, т.к. он, как и любой другой детектив, строил общую версию исходя из более мелких, частных фактов и улик, но это мое личное мнение и оно к делу не относится :).

Так к чему я это все? Да к тому, что waterfall - это как раз способ разработки методом сверху-вниз. То есть мы сначала получаем от клиента по возможности все требования (при помощи бизнес-анализа), анализируем их, оцениваем, строим архитектуру, документируем это все - и вперед, арбайтен. От общего к частному или, иными словами, посредством дедукции. Именно на этом подходе работает большинство нормальных человеческих проектов, начиная от строительства дома и заканчивая запуском спутника в космос. Но разработка ПО - это даже близко не строительство дома, хотя определенные аналогии провести можно. Все-таки поменять в последний день логику работы приложения намного легче, чем соорудить автостоянку под уже построенным зданием. То есть в разработке ПО есть такое понятие как гибкость (agility). Ко всему прочему, заказчики (а это может быть кто угодно) - люди зачастую не очень понимающие то, что они хотят получить в результате, а если и понимающие, то склонные менять свое мнение, иногда очень кардинально. Так что же, заставлять их выдавать на-гора все функциональные и нефункциональные требования к приложению ДО начала разработки?

Вот собственно из этого всего и родились различные agile-подходы к разработке ПО. А потом появился небезызвестный манифест - и пошло-поехало. Так что же такое agile и чем он отличается от обычной водопадной или итеративной практики разработки? Ну, отличий, конечно, много, но я бы в первую очередь сказать, что это - процесс разработки ПО снизу-вверх, то есть индуктивный процесс. С самого начала мы очень мало знаем о том, что мы вообще строим, иногда даже так же мало, как и заказчик. Но нам по барабану. Мы берем то, что есть, тратим не очень много времени на планирование - и стартуем. Сделали что-нибудь стоящее, показали заказчику, определили следующие задачи - и поехали дальше. И так до тех пор, пока заказчик не получит то, что хотел. При этом необходимо отметить, что гибкие методологии, в отличие от своих "жестких" товарищей, все же имеют свою жесткость - жесткость правил, по которым ведется разработка. Если у вас нет unit-тестов - вы обломаетесь на первых же серьезных рефакторингах системы. Если у вас нет code review или pair programming - вам будет сложно организовать коллективное владение кодом, что можем повлечь серьезные проблемы, если кто-то из ведущих разработчиков вдруг заболеет. Если вы забиваете на product backlog - у вас в скором времени возникнет каша с требованиями (впрочем, каша с требованиями возникает и тогда, когда они есть, но на их обновление точно так же забивают). Если вы не проводите ежедневные митинги - ваши разработчики будут дублировать действия друг друга и изобретать велосипеды сотнями, а код превратится в мусорку. А все потому что работа ведется в условиях самоорганизовывающейся команды (self-management team) и нет никого, кто бы мог сверху спустить план, в котором будет четко сказано, кто, что и когда должен делать.

Надо сказать, это сложно, т.к. получается, что каждый член команды должен не просто ответственно работать, но еще и быть достаточно подкованным с технической точки зрения. Так что перед тем, как с криками "ура, я познал истину и теперь все мы будем жить счастливо" бежать к своей команде, подумайте сначала, а сможет ли она работать в таких условиях? И, что еще более важно, подумайте, а надо ли оно вам? Если у вас есть нормальные требования и клиент знает, чего он хочет, то зачем городить огород? Возьмите какой-нибудь UP - и будет вам счастье. Если же этого нет, то добро пожаловать в мир agile :)

PS. А вообще, серебряной пули нет, вы же знаете. Успех или неудача вашего проекта зависит не только от того, какой процесс вы выберете, но и от того, насколько вы сможете его кастомизировать под свои нужды в процессе работы. А для этого нужно просто уметь думать.

Отзыв о книжках: DDD Нильссона и Scrum из окопов Книберга

Последние несколько недель были плодотворными в плане чтения книжек. Дочитал "Применение DDD и шаблонов проектирования: проблемно-ориентированное проектирование приложений с примерами на C# и .NET" Джимми Нильссона и прочитал "Scrum и XP: заметки с передовой" Хенрика Книберга. Спешу поделиться отзывами, так как уже давно ничего толкового не читал.

ddd 

Про первую книгу я уже писал относительно недавно. Рекомендую всем, кто чувствует, что ему стало тесно в рамках стандартных технологий и подходов, а архитектура и дизайн его приложений хромают на обе ноги. Domain Driven Design - это радикально другой подход к разработке ПО, поэтому книжка отлично освежает голову, дает совершенно другую перспективу и точку зрения на привычные вещи. Нильссон дает общее представление о DDD, а также на примерах показывает, как можно разрабатывать реальные приложения с использованием DDD, TDD, и различных паттернов проектирования. Автор начинает с простых вещей, вводит рабочий пример и потихоньку продвигается по пути его реализации, задевая вопросы архитектуры, модели предметной области, инфраструктуры для сохраняемости, архитектурных паттернов, unit-тестов, правил, аспектно-ориентированного программирования, пользовательского интерфейса, а также немного проходится по NHibernate. В общем и целом, после прочтения книжки в голове появляется намного более серьезное понимание всех вышеперечисленных вопросов, и даже если DDD вам не подходит, вы вынесете из книги много новых знаний. Сразу хочу предупредить, что это не Эванс, здесь нет серьезной теоретической базы, лишь практика, практика, и еще раз практика. Ну, и ложка дегтя: русский перевод этой книги просто ужасен. Не понимаю, как можно было перевести кучу названий паттернов (чего стоят только прецеденты использования и неведение сохраняемости), а также стандартных терминов (DDD - ППО, проектирование предметной области, TDD - РПТ, разработка посредством тестирования). Да и ладно бы, но там не только это перевели, поэтому постоянно приходится думать, как эта фраза звучит по-английски и что это за очередной термин такой. По всей видимости, после перевода книжку не проверил эксперт в данной предметной области. Поэтому читайте оригинал :)

scrum

Вторая книжка была переведена сообществом Agile Ukraine, за что ребятам огромное человеческое спасибо! Причем, больше спасибо за то, что они разрекламировали ее, потому что в реальности, как и предыдущая книга, эта читается на английском легко и непринужденно. Книжка переведена просто замечательно, в отличие от перевода того же Нильссона. Сама же книга повествует об опыте Хенрика Книберга в постановке и использовании Scrum'а в нескольких командах. Хенрик последовательно проходится по вопросам планирования спринта, работы с product backlog'ом, ролям команды, ведения burndown-диаграммы, проведения scrum-митингов, демо, ретроспектив, а также, что более важно, объясняет, как работать по Scrum-у в условиях fixed-cost проектов, показывает родство Scrum и XP, и делится опытом постановки процесса QA и работы с несколькими командами. В общем и целом, для человека, который знаком с другими процессами разработки ПО, и который имеет не только положительный, но и отрицательный опыт, эта книга может стать хорошим стимулом попробовать поднять Scrum у себя на проекте. Тем более, если у вас ничего другого нет :) В общем, рекомендую не только PM'ам.

Friday, February 6, 2009

Летаргический сон украинского .NET-сообщества

С сожалением вынужден констатировать, что украинское .NET-сообщество либо мертво, либо находится в глубокой и беспробудной спячке. Откуда такие неутешительные выводы? Да так, личные наблюдения. Скажите, пожалуйста, проходила ли когда-нибудь в Украине нормальная полноценная .NET-конференция? Нет, я не про DevDays, которые, надо признаться, в этом году в Киеве уже были похожи хотя бы на что-то более-менее взрослое, а не на обычную рекламу последних разработок Microsoft. Я про что-то более реальное, более осязаемое, с серьезными темами и докладами, с серьезными докладчиками. А давно ли к нам с докладами наведывались реальные девелоперы из Microsoft или хотя бы из западного сообщества? Говорите, что им тут делать, они по таким мероприятиям не ездят? А если я вам скажу, что многие из них ездят не только в Европу или там Австралию, но и в Южную Африку, Египет, Пакистан, Турцию, Болгарию, Ливан? А потом делятся в подкастах, что, мол, вот какая в Софии была классная конференция, а в Пакистане у нас аж 3-тысячная аудитория была. И в Россию приезжают, что делает честь нашим северным соседям не только потому, что они их приглашают, но и потому что они способны организовать что-то серьезное, вроде той же Платформы, РИТ или других конференций. Да, это еще не PDC, но это уже хоть что-то, вы не находите? Хорошо, а много ли у нас других конференций или встреч? Не знаю, как в Киеве или Львове, а вот в Харькове есть еще IT Talk'и, которые проводятся под чутким руководством Жени Устименкова, встречи UNETA, которые были бы невозможны без Вовы Лещинского и еще нескольких человек, за что им честь и хвала, и все. Да, еще иногда заезжают с приветом из Киева QA Club и Agile Gathering. Вот теперь точно все! А теперь еще одно задание: скажите, пожалуйста, сколько вы знаете украинских .NET-блоггеров, которых интересно читать. Много насчитали? Угу, вот мне тоже как-то пальцев на двух руках хватило...

С чего это я так разошелся? Да, кто его знает, наболело, наверно. Вчера в Харькове на чем-то наподобие DevDays были доклады по Windows Azure. Знаете, сколько было людей? Человек 50, не больше. Из них человек 30 - знакомые лица со встреч UNETA, то есть костяк, который ходит на все подобные мероприятия. И это в городе, где по самым скромным подсчетам больше 2 тысяч .NET-разработчиков. А почему? А потому что: а) реклама мероприятия была дана меньше чем за неделю до его проведения и прошла, по-моему, только по рассылке dev.net.ua, многие мои друзья даже не знали о том, что что-то будет, б) в Харькове у людей неоднозначное мнение об уровне подобных мероприятий: серьезные разработчики предпочитают их игнорировать, как и встречи UNETA, которые в последние несколько лет превратились в собрания студентов. Ну что за организация, в самом деле?! Почему подобные мероприятия не анонсируются как-то более серьезно, почему не рассылается реклама по разным фирмам, почему GlobalLogic может собрать пару тысяч человек на Программанию (пусть большинство из них студенты - не важно), а Microsoft Украина - нет? Ну да ладно, проблему с рекламой поправить не сложно. А вот как доказать сильным разработчикам и их работодателям, что этот день (или вечер) пройдет для них не зря? Что они услышат интересные доклады, из которых смогут узнать что-то новое для себя, что-то, что им поможет в их работе. Что они смогут встретиться с интересными людьми, пообщаться, поделиться опытом и приобрести его. Что они смогут сами поучаствовать в докладах, если видят в себе силы и имеют желание! И не говорите мне, что у нас нет ребят, способных на это. Только в одном Харькове среди моих близких и дальних знакомых я знаю десятки подобных людей, которые просто не хотят ходить на наши “местные” конференции из-за их слабого уровня.

Как, в конце-концов, сформировать в Украине или отдельно взятом городе настоящее сообщество, которое сможет не просто вместе двигаться вперед, но и двигать вперед всю отечественную разработку? Может, стоит обратиться за опытом к профессионалам хотя бы того же Microsoft, которые уже собаку съели на этих вопросах и построили у себя в Штатах или Европе реально сильные сообщества. Ведь все реально, было бы только желание. Существуют же online-сообщества типа того же RSDN уже в течение многих лет, и держаться как-то. Так чем мы хуже-то?

Ведь сообщество - это не только "тусовка", это еще и место, где можно подойти и посоветоваться с экспертом в определенной области, услышать про новинки, в конце-концов, самому подготовить доклад о чем-то, если есть желание. А потом пойти и внедрить это на практике у себя в работе. Ведь это, возможно, один из самых эффективных способов повышения своего уровня и уровня окружающих тебя людей. Не говоря уже о том, что это фан. Без подобного роста над самими собой, без обмена опытом, без проб и ошибок мы так и останемся страной третьего мира в разработке ПО. И так и будем заниматься аутсорсингом саппортных проектов или банальных веб-сайтиков и ERP-систем, которыми уже никто не хочется заниматься на Западе. И не будет у нас своих разработок, своих исследований, своих продуктов и своих достижений.