На переднем крае

Объекты и искусство моделирования данных

Дино Эспозито

 

Dino Esposito Многие из современных приложений создаются на основе одной модели данных, обычно сохраняемой в хранилище с помощью средств ORM (object-relational mapper). Но иногда (по разным причинам) вам может потребоваться более высокая гибкость, для чего необходимо несколько моделей. В этой статье я рассмотрю некоторые стратегии, которые позволяют справляться с этими ситуациями и разрабатывать более надежные приложения с большим числом уровней.

Применение разных моделей, очевидно, усложняет приложение в целом, но это усложнение положительное, вызванное необходимостью и делающее весь проект более управляемым. Архитектор должен понимать, когда одна модель данных не годится на все случаи использования разрабатываемого приложения. В разных частях приложения могут быть разные модели данных. Способ представления данных на уровне UI отличается от организации данных на промежуточном уровне, а та в свою очередь может отличаться от физической структуры данных в хранилище.

Однако в течение многих лет разработчики применяли лишь одну модель данных на всех уровнях. Многие из нас выросли на реляционных базах данных и связанных с ними методах моделирования. Было естественным тратить массу усилий на детальную разработку модели, основанной на отношениях и правилах нормализации. После этого любые данные, хранящиеся в реляционной базе данных, извлекались и передавались с использованием структур в памяти, аналогичных таковым в базе данных. Название этого универсального шаблона данных — Record Set (bit.ly/nQnyaf), а ADO.NET DataSet был великолепным образцом его реализации.

Подход на основе таблиц постепенно был задвинут в угол из-за растущей сложности приложений. В итоге появились ORM-средства — по двум основным причинам. Первая — работать с объектами легче, чем с универсальными супер-массивами данных вроде набора записей. Вторая — эффективность. Цель ORM заключается в сопоставлении объектной модели с реляционной схемой. Используя ORM, вы фактически делаете так, что объектная модель кажется в приложении настоящей базой данных.

Как проектируют такую модель? И действительно ли следует использовать только одну модель в каждом приложении? Давайте поищем ответы на эти вопросы.

Одна модель не всегда годится на все случаи

В недавно выпущенной Entity Framework (EF) версии 4.1 включили поддержку программирования Code First («сначала код»), которое, как и предполагает само название, дает возможность разработчикам, использующим Microsoft .NET Framework, применять к моделированию данных подход с предварительным написанием кода. В принципе, это означает, что вы начинаете с написания классов Plain Old CLR Object (POCO), которые моделируют предметную область приложения. На втором этапе эти классы сопоставляются с каким-то хранилищем, и ORM-средство (в данном случае — EF) берет на себя все детали взаимодействия. ORM предоставляет язык запросов (LINQ to Entities), транзакционную семантику (объекты xxxContext в EF) и базовые API для операций создания, чтения, обновления и удаления (create, read, update and delete, CRUD) с поддержкой параллельной обработки, отложенной загрузки и планов выборки (fetch plans).

Итак, у вас есть модель, и вы знаете, как ее сохранить и запросить от нее информацию. Является ли она моделью, которую можно использовать повсюду в вашем приложении? Точнее, является ли она моделью, которую вы можете в конечном счете донести до презентационного уровня? Это действительно болевая точка, в которой теория и практика сильно расходятся: все зависит от его величества контекста. Позвольте мне предложить конкретный пример, связанный с популярной технологией ASP.NET MVC. Единственная причина, по которой я использую ASP.NET MVC вместо, скажем, Silverlight, состоит в том, что в ASP.NET MVC в названии скрыто слово «Model» (M в MVC), — меня слишком часто спрашивали на занятиях и конференциях, какое именно место занимает модель в приложении ASP.NET MVC.

Сегодня, даже «спустя три версии», слишком во многих учебных пособиях по ASP.NET MVC настаивают на применении только одной модели для запросов, обновлений и отображения пользователю. Так же предлагается хранить определение модели в основном проекте. Работает ведь — в чем проблема? Проблема не с решением, которое работает вполне эффективно и которое я сам нередко использовал и буду использовать. Действительно есть множество реальных, но простых и короткоживущих приложений (не только демонстрационных и учебных программ), которые можно разрабатывать на основе простых шаблонов. Проблема с применением только одной модели кроется в другом: разработчики, приступающие к изучению этой технологии, получают посыл: использование всего одной модели в одном месте является предпочтительным (или даже рекомендованным) способом. А на самом деле это лишь особый случай — очень простой сценарий. Если ваш вариант подпадает под эту категорию, вам более чем повезло. Иначе вы застрянете и будете возмущаться.

Более универсальная архитектура

Начнем с использования несколько других названий. Давайте назовем сохраняемую вами модель данных моделью предметной области (domain model), а данные, которыми вы управляете в представлении, — моделью представления (view model). Следует отметить, что модель предметной области не является нейтральным термином в программном обеспечении, так как относится к объектной модели, проектируемой согласно ряду дополнительных правил. В рамках этой статьи я не использую то значение этого термина, которое вытекает из методологии Domain-Driven Design (DDD). В данном случае модель предметной области я рассматриваю просто как сохраняемую вами объектную модель (еще одним эквивалентом могла бы быть модель сущностей) и менее запутанный термин.

Вы используете классы в модели сущности в серверной части приложения, а классы в модели представления — на презентационном уровне. Однако заметьте, что в ASP.NET MVC презентационным уровнем является контроллер. Контроллер должен принимать данные, готовые для вывода в UI. Компоненты промежуточного уровня принимают и возвращают объекты модели представления и используют внутри этого уровня объекты сущностей. Схема зависимостей в типичном проекте многоуровневого приложения приведена на.
рис. 1.

Рис. 1. Связи между уровнями

Связи между уровнями

Презентационный уровень (т. е. класс отделенного кода или контроллера) ссылается на прикладную логику, а именно на компонент, который реализует сценарии использования данного приложения. Прикладная логика с технической точки зрения относится к бизнес-уровню и в очень простых случаях может быть объединена с презентационным уровнем. Именно так и подают в некоторых учебных пособиях, которые либо слишком просты, чтобы можно было прочувствовать необходимость изоляции прикладной логики в собственном уровне, либо плохо написаны и разделяют прикладную логику между презентационным уровнем и уровнем доступа к данным (data access layer, DAL).

В сборке прикладной логики реализуется шаблон уровня сервисов, и она отделяет два интерфейсных уровня: презентационный и доступа к данным. Прикладная логика ссылается как на модель сущностей (ваши классы предметной области), так и на DAL. Прикладная логика управляет DAL, классами предметной области и сервисами, формируя тем самым ожидаемое вами поведение. Прикладная логика взаимодействует с презентационным уровнем через объекты модели представления, а с DAL — через объекты предметной области. В свою очередь DAL ссылается на модель и сборку ORM.

Несколько слов об Entity Framework

Обсудим эту архитектуру, рассматривая EF как ORM. EF — не просто ORM; помимо типичной работы ORM, она, как и предполагает ее название, предлагает инфраструктуру для создания модели. Неплохая идея, но не следует забывать, что мы шагаем по двум разным уровням: бизнес-уровню и DAL. Классы относятся к первому уровню, а механизм хранения — к DAL. В случае EF механизмом хранения (сборкой ORM) является system.data.entity и ее зависимости, в том числе класс ObjectContext. Иначе говоря, если только вы не используете POCO-классы и Code First, то скорее всего придете к модели предметной области, имеющей зависимость от ORM. Модель предметной области должна использовать POCO, т. е. помимо всего прочего она должна быть самодостаточной сборкой. Интересное обсуждение по этой тематике можно найти на Stack Overflow по ссылке bit.ly/mUs6cv. Подробнее о том, как отделять сущности от контекста в EF, см. в блоге группы ADO.NET статьи «Walkthrough: POCO Template for the Entity Framework» (bit.ly/bDcUoN) и «EF 4.1 Model & Database First Walkthrough» (bit.ly/hufcWN).

Из этого анализа вытекает, что POCO-атрибут важен для классов предметной области. POCO, конечно, указывает класс безо всяких зависимостей за пределами своей сборки. Смысл POCO в простоте, и его использование никогда не вредно. Игнорирование POCO так или иначе ведет к жесткому сопряжению уровней. Жесткое сопряжение не ядовито и не убьет вас мгновенно, но может постепенно погубить ваш проект. Почитайте мою статью в прошлом номере (msdn.microsoft.com/magazine/hh394145).

Что такое модель?

Создавая объектную модель, вы создаете библиотеку классов. Вы можете организовать свою объектную модель по нескольким шаблонам, но, по сути, все они сводятся к выбору между подходом, ориентированным на таблицы, и подходом, ориентированным на объекты.

Как вы проектируете свои классы? Какие возможности должны быть у них? В частности, должны ли ваши классы знать об устройстве базы данных? Должны ли они включать логику (т. е. методы) или ограничиваться только свойствами? При этом вы можете опираться на два основных шаблона: Active Record и Domain Model.

В шаблоне Active Record ваши классы почти полностью моделируют таблицы базы данных. Как правило, у вас есть один класс на каждую таблицу и одно свойство на каждый столбец. Еще важнее, что классы отвечают за свое сохранение и имеют собственную простую, минимальную логику предметной области.

В шаблоне Domain Model ваши классы нацелены на концептуальное представление предметной области задачи. Эти классы не имеют никаких связей с базой данных и обладают как свойствами, так и методами. Наконец, они не отвечают за свое сохранение. Если вы предпочтете подход Domain Model, сохранение нужно делегировать другому уровню — DAL. Вы можете самостоятельно написать этот уровень, но это занятие вряд ли доставит вам удовольствие. Хорошо продуманный DAL для библиотеки, разработанный согласно шаблону Domain Model, — это почти то же самое, что средство ORM. Так почему бы не воспользоваться одним из существующих средств ORM?

Если вы используете автоматический генератор кода, EF предоставляет вам объектную модель, состоящую из классов, которые имеют только свойства. Применяемый шаблон явно не относится к Active Record, но по умолчанию в классах нет методов. К счастью, классы помечаются как частичные, что позволяет вам формировать немного более функциональную модель предметной области, добавляя логику через частичные классы. Если вы выбрали подход Code First, тогда вы полностью берете на себя написание исходного кода своих классов — от начала до конца.

Классы в объектной модели, имеющие лишь свойства, часто называют анемичной моделью предметной области (anemic domain model).

Репозитарии

Хороший стиль, заслуженно завоевавший популярность, — обертывание кода доступа к данным в классы фасада, называемые репозитариями. Репозитарий(repository) состоит из интерфейса и класса реализации. Как правило, в модели для каждого значимого класса есть свой репозитарий. Значимый класс (significant class) в объектной модели — это класс, управляющий своим сохранением и автономный в модели предметной области (т. е. независимый от других классов). Например, Customer, в принципе, является значимым классом, тогда как OrderDetails — нет, поскольку вы всегда используете позиции заказа только при наличии самого заказа. В DDD значимый класс считается агрегатным корнем (aggregate root).

Чтобы улучшить проект, вы можете установить зависимость между прикладной логикой и DAL через интерфейсы репозитария. Репозитарии можно потом встраивать в прикладную логику. На рис. 2 показана архитектура, где DAL основан на репозитариях.

Рис. 2. Применение репозитария в DAL

Применение репозитария в DAL

Репозитарии делают возможным встраивание зависимости в DAL, позволяя легко отключить текущий модуль, обеспечивающий логику сохранения, и заменить его собственным. Это очень удобно в тестировании, но возможности определенно не ограничиваются только этим. Архитектура, основанная на репозитариях, позволяет имитировать DAL и изолированно тестировать прикладную логику.

Репозитарии также являются прекрасными точками расширения для ваших приложений. Заменяя репозитарий, вы можете заменить механизм сохранения таким способом, который является прозрачным для остальных уровней. Но главное здесь не в переключении, скажем, с Oracle на SQL Server — этот уровень гибкости уже предоставляется средствами ORM, — а в возможности перехода с текущей реализации DAL на нечто иное, например облачный API, Dynamics CRM, какое-то решение на основе NoSQL и т. д.

Какими не должны быть репозитарии

Репозитарий — часть DAL; как таковой, он не должен знать о прикладной логике. Возможно, это банальность, но я сейчас слишком часто вижу такие архитектурные схемы, которые основаны на презентационном уровне и репозитариях.

Если ваша логика проста или в основном соответствует поддержке операций CRUD, тогда такая схема вполне годится. Ну а если это не ваш случай? Тогда вам определенно нужно какое-то место в промежуточном уровне, где вы развертываете свою прикладную логику. И поверьте мне: за всю свою карьеру я видел лишь несколько приложений, которые выполняли только CRUD-операции. Хотя, возможно, мне просто не везло…

Заключение

Современные приложения часто проектируются вокруг модели данных, которую то или иное ORM-средство потом сохраняет в некое хранилище данных. Это нормально для серверной части приложения, но тогда не удивляйтесь, если UI потребует от вас работать со значительно отличающимися агрегатами данных, которые отсутствуют в исходной модели. Вам придется создавать все эти новые типы, существующие исключительно для использования в UI. И все это закончится формированием полностью параллельной объектной модели — модели представления. Если вам удастся сократить эти две модели до одной, пользуйтесь и радуйтесь. Ну а если нет, то, надеюсь, я сумел прояснить в этой статье некоторые туманные моменты.


Дино Эспозито  (Dino Esposito) — автор книги «Programming Microsoft ASP.NET MVC3» (Microsoft Press, 2011) и соавтор «Microsoft .NET: Architecting Applications for the Enterprise» (Microsoft Press, 2008). Проживает в Италии и часто выступает на отраслевых мероприятиях по всему миру. Читайте его заметки на*twitter.com/despos.*

Выражаю благодарность за рецензирование статьи экспертам Андреа Салтарелло (AndreaSaltarello) и Диего Вега (DiegoVega).