среда, июля 30, 2008

Как EF влияет на дизайн ASP.NET приложений

Если создавая ASP.Net приложение, вы сумели оторваться от дизайнера ASP.NET страниц, и уже не складываете на странице датасеты, и объекты соединения с БД, значит вы пошли по пути структуризации дизайна вашего web приложения. Наверняка вы выделили классы или модули (сборки) для чтения записи данных в БД, и отдельные классы для представления этих данных и размещения бизнес логики. Возможно вы пошли дальше, и в слое представления выделили логические классы контроллеры, в которые поместили логику организации последовательности действий (workflow), создания представления и валидации пользовательского ввода, оставив в aspx ascx файлах только разметку. А может вы не стали этого делать и функции контроллера оставили в aspx формах.

Как повлияет на сложившуюся архитектуру ваших ASP.NET приложений использование Entity Framework? Хорошая новость состоит в том, что EF наверняка легко впишется в ваше приложение при использовании любого дизайна (даже если вы дружите с дизайнером ASP.NET страниц :).

Однако EF незатейливо подталкивает нас к использованию "анемичной модели", когда классы домена (данные) отдельно, а бизнес-операции (сервисы) отдельно. И это достаточно удобная организация кода.

В качестве классов домена используем сущности EF. Запихивать бизнес логику в partial классы EF я бы не советовал. На этот счет есть много резонов, главные из них:
- вы увеличиваете зависимости между сущностями.
- часто это нарушает принцип single responsibility
- структуры данных меняются чаще чем бизнес правила и объединив то и другое в одном классе вы получите проблемы при внесении изменений.
В partial классы для сущностей EF полезно помещать всякие хитрые свойства (вычисляемые, виртуальные и т.д.), а также логику пре- и пост-валидации значений персистентных свойств (это делается при помощи partial methods, генеримых дизайнером EF).

Бизнес логику лучше помещать в классы сервисы, группируя их по функциональному признаку. Методы бизнес логики строятся по шаблону transaction script. При этом ObjectContext создается либо в для каждой бизнес-операции (внутри метода сервисного класса), либо на уровне контроллера в слое представления, и тогда он предается в методы бизнес логики в качестве параметра.

Какой вариант выбрать - вам решать. Однако,если вы создаете полноценный Service layer - то ObjectConext не должен болтаться в параметрах бизнес-методов. Использование ObjectConext на уровне контроллера в слое представления позволяет более тонко гранулировать бизнес логику и получить за счет этого более эффективное решение (т.е. вместо "больших" бизнес-методов, которые сосредоточенно что-то там делают внутри, вы имеете много "маленьких" бизнес методов, которые получают на вход контекст и вы можете комбинировать их, выстраивая нужную логику). Эффективность может увеличиться в числе прочего, за счет того, что время жизни ObjectConext будет больше, и следовательно, больше возможностей воспользоваться преимуществами кэширования объектов. Важно понимать, что в этом случае вы платите за повышение эффективности большей связностью между уровнями бизнес логики и представления, и вам, в первую очередь, будет сложнее вносить изменения в существующее решение.

Не следует опасаться создавать экземпляры ObjectConext, не следует пытаться создать для хранения контекста каки либо потоковые переменные и, тем более, синглтоны. Дело в том что EF достаточно эффективно кэширует на уровне домена приложения (Application Domain) внутри себя всю метаинформацию, и после создания первого экземпляра ObjectConext, последующие создаются достаточно быстро. Кроме того, ObjectConext не является потокобезопасным, поэтому не следует пытаться использовать глобальный ObjectConext на уровне приложения или сессии.

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

8 комментариев:

stddjuffin комментирует...

[1]Не следует опасаться создавать экземпляры ObjectConext, не следует пытаться создать для хранения контекста каки либо потоковые переменные

[2]Следует избегать ситуаций, когда у вас создаются параллельно несколько ObjectConext в одном потоке, а также создания вложенных ObjectConext.

Не противоречат ли эти два утверждения?

Sergey Rozovik комментирует...

Отчасти, да, противоречат. Вы не должны создавать глобальные экземпляры ObjectConext из-за опасений, что создание экземпляра накладная операция. Вы можете создавать ObjectConext там где вам это надо. Однако при этом надо следить за тем, чтобы избегать создания нового ObjectConext там где выше по стеку вызовов уже создан более глобальный ObjectConext. Лучше передать уже готовый контекст в параметрах.

Александр Кондуфоров комментирует...

Хорошее описание базовой техники работы с EF. У нас приложения строятся практически полностью по этой модели за исключением некоторых моментов. Непосредственно над доменной моделью мы развернули свой небольшой прокси для доступа к данным, который отвечает исключительно за CRUD, а уже его непосредственно юзают классы-сервисы, которые, в свою очередь, используются контроллерами. Создание прокси-класса дало нам возможность вынести в этот слой некоторые общие операции и было следствием использования Add-In framework, но в обычном приложении можно обойтись и без него. Контекст мы создаем либо на риквест, либо на несколько риквестов (в сессии), если речь идет о многошаговом диалоге.

stddjuffin: Нет, не противоречит. В одном потоке не может быть несколько запросов к одному и тому же ObjectContext. Они могут исходить из нескольких потоков и вот как раз этого и нужно избегать, потому что ObjectContext просто падает с ошибкой о проблемах с коннекшном.

Александр Кондуфоров комментирует...

Сергей, не видел ваш ответ. Разве такая проблема, что вы описываете, вообще существует? Мы в принципе, не пробовали создавать несколько контекстов в одном потоке, но несколько контекстов в разных потоках сосуществуют вполне сносно и я не вижу особых проблем для того, чтобы они не смогли работать в одном потоке.

Sergey Rozovik комментирует...

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

Warvick комментирует...

Почти со всем согласен.
Сергей, интересует вопрос по поводу организации моделей. "По сути" EF edmx все равно мапится на БД. Встает вопрос. Всю базу мапить в одном файле - накладно, разносить по разным моделям согласно схемам БД - чуть лучше, но встает вопрос как быть с таблицами, которые имеют связи с таблицами во всех схемах. Пока ничего лучше, как включать их view и ручками настраивать Navigation в каждой моделе я не придумал, может подскажете ?

Sergey Rozovik комментирует...

to Warvick
К вопросу по поводу того мапить всю БД в одну EDM (Entity Data Model) или разбивать на несколько.
Сразу хочу заметить. Даже если вы по каким то причинам используете в БД несколько схем. это вовсе не повод разбивать таким же образом EDM. Прежде всего, потому, что концепция EDM призвана позволить вам абстрагироваться от БД на уровне бизнес логики и домена приложения, и за счет этого построить более эффективную модель.
Когда стоит разбивать EDM на части? Это имеет смысл делать в том случае если в вашей системе присутствует горизонтальное разделение на модули по функциональному признаку. Что это значит? Мы уже привыкли что система делится на слои: Data Access Layer, Business Logic Layer, Services Leyer, Presentation Layer и т.д. Это все вертикальное разделение. Если система достаточно велика, возможно разделение ее на модули по функциональному признаку, например, модуль учета заказов, модуль учета запасов и т.п. Такие модули могут быть достаточно автономны, вплоть до того, что могут выполняться в отдельных физических процессах. В этом случае может быть оправданным создание локальных моделей, которые будут включать в себя только подмножество необходимых сущностей. Конечно, все такие локальные модели будут совместно использовать часть общих сущностей (например, справочники). При этом в каждой локальной модели общие сущности будут представлены своими классами, что создает определенные неудобства в точках сопряжения (представьте, что у вас "продукт" будет представлен одновременно двумя классами: MySystem.Orders.Product и MySystem.Products.Product). Конечно, это неудобно. Видимо, если ваша БД не содержит сотен таблиц, имеет смысл создать одну EDM, выделить ее в отдельный модуль (сборку) и использовать во всей системе.
Исключение составляют физически обособленные части системы (например, у вас есть служба нотификации, которая работает как windows service, и нуждается для своей работы лишь в паре таблиц). Другое исключение, это в достаточной степени инкапсулированные вертикальные инфраструктурные сервисы, например, служба логгирования, или сервис авторизации.
Впрочем всегда надо смотреть по конкретным обстоятельствам. Основные критерии. удобство использования, внесения изменений, и расширения, а также соображения безопасности (как в случае со службой авторизации).

Mike Chaliy комментирует...

>> - вы увеличиваете зависимости между сущностями.
Гм, а как предложенная модель с сервисами в этом помогают? ИМХО она добовляет еще и одну зависимость ;). И почему это плохо?

>>- часто это нарушает принцип single responsibility
Хм, тоесть операция "поковыряться в носу" нарушает принцип single responsibility для рутовой сущьсности "человек", с подчиненной ему сущьности "нос"...

>> - структуры данных меняются чаще чем бизнес правила и объединив то и другое в одном классе вы получите проблемы при внесении изменений.
Это как? Например я перименовал свойство - что в ентити что в сервисе мне придеться чето поменять; я переименовал сущьность - опять же и там и там надо перименовать; я удалил сущьность.. - тож понятно. Вобщемто ИМХО слегка надуманно...

Теперь так сказать плюсы бизнес логики в сущьностях по сравнению с транзакшен скриптами(сервисах).

Тестируемость. - Раз мы уже решили использовать ентити как ентити, то тестировать ентити намного легче, так в отличии от сервиса у ентити ненадо дублировать(моки) репозитарии(конеткст). Очень рекомендую поглядеть на DDD, там про это написано намного больше.

single responsibility. - Да, какраз в сущьности этот принцип потдерживать в перспективе проще. Наверное все сталкивались с сервисами типа КастомерМенджер в кторых полторы сотни бизнес методов. Хотя вобщемто это больше проблема уровня програмиста....

Само документируемость. - Намного проще найти метод "поковыряться в носу" в сущьности "человек", чем сервис "ковырятор в носу". Воркфлоу поиска значительно легче.

Поддержка версионирования. - Кастомер уточнил что ковыряться нигде не надо. Мы удаляем метод "поковыряться в носу" и чекинимся. Через десяток интераций кастормер пердымывает... Понятно что по истории изменения мы быстренько все находим. Теперь таже ситуация с удаленным сервисом. Так быстро все не получиться.

unambiguous language - когда домен непротеворичиво отображет бизнес процесы. Типа того что человек ковыряеться в носу. А не использует какой то мифический "носо ковырятор". Опять же из DDD (это наверное одно из самых мощных плючов неанемичных моделей).

Лакончиность - в подходе с сервисами очень сложно потдерживать нормальные имена, получаеться куча мусора. Один из примеров єто дублирование, смотрите - сущность: "человек", сервис: "сервис для ковыряния в носу человека", метод сервиса: "поковыряться". А теперь сравните - сущность: "человек", метод сервиса: "поковыряться в носу". Есть и другая крайность - "сервис для человека" - понятно что слово сервис в нем мусор без какой либо симантики (http://www.c2.com/cgi/wiki?DontNameClassesObjectManagerHandlerOrData).

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