среда, июля 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. Это может приводить к коллизиям при обновлении и сохранении данных.

пятница, июля 18, 2008

А если бы у него был компьютер...

IT-шникам свойственно преувеличивать значение своих технологий в бизнесе и в жизни. Вот хороший анекдот в тему:

Мужик приходит устраиваться дворником в компанию Microsoft.

Менеджер отдела кадров сначала, задает ему несколько вопросов, потом проводит небольшой тест (предлагает подмести часть территории) и наконец обявляет решение:
— Вы приняты, оставьте ваш электронный адрес, чтобы мы могли вам сообщить в какое место и в какой день вам нужно будет в первый раз прийти на работу.
— Но у меня и компьютера-то нет, — растерянно отвечает мужик, — не то что электронного адреса.
— В таком случае мы не можем взять вас на работу, поскольку виртуально вы не существуете.
Мужик выходит грустный, не зная что делать, когда в кармане осталось всего 10 долларов. Однако тут ему в голову пришла мысль: он заходит на рынок и покупает 10 кг помидоров.

Затем он начинает ходить по домам и предлагать товар, и меньше чем за 2 часа ему удается удвоить капитал. После того как он повторил то же самое еще 3 раза, у него в кармане было уже 160 баксов. И тут он понимает, что с такими доходами вполне можно жить и без работы! Каждое утро он выходит из дома все раньше и возвращается все позднее, каждый день удваивая, а то и утраивая капитал.

Через какое-то время он покупает машину, затем грузовик, а еще через некоторое время открывает фирму по доставке товаров населению.

Спустя 5 лет он уже является владельцем крупной сети супермаркетов.

И тут, задумавшись о будущем, он вдруг решил застраховать свою жизнь и жизнь всей своей семьи.

После переговоров со страховым агентом тот просит его оставить электронный адрес, на который можно было бы отправить наиболее выгодное предложение, на что коммерсант, как и несколько лет назад, отвечает, что у него нет ни электронного адреса, ни даже компьютера.
— Это удивительно, — недоумевает страховой агент, — у вас такой крупный бизнес и нет электронного адреса! Вы только представьте себе, кем бы вы стали, если бы у вас был компьютер!
Поразмыслив, коммерсант отвечает:
— Я бы стал дворником компании Microsoft.

четверг, июля 17, 2008

Terrarium 2.0 теперь в исходных кодах!

Microsoft Windows SDK Blog сообщает что .NET Terrarium 2.0 source code now available!

Не многие наверное помнят, что такое Terrarium. Для тех, кто не в курсе - была такая сетевая игра для программистов. Microsoft запустила Terrarium вместе с выходом первой версии .Net Framework с целью популяризировать новую платформу и языки программирования (C# и VB.NET) среди программистов. Суть игры состояла в том, что надо было используя Terrarium SDK написать класс (унаследовавшись от базового и реализовав нужные интерфейсы), представляющий собой травоядное или хищное животное. Алгоритм поведения задавался кодом, а характеристики (быстрота, агрессивность, незаметность, скорость размножения и т.п.) задавались посредством нового тогда механизма атрибутов. Класс надо скомпилировать, а полученную сборку загрузить на сервер игры. После этого на сервере создавалось 10 экземпляров животных твоего класса (Reflection!) и они начинали "жить", ползая по игровому полю твоего клиента. Клиент игры представлял собой Windows Forms приложение и соединялся с сервером посредством Remoting. Фишка состояла в том, что при благоприятных условиях, твои животные начинали размножаться, а при неблагоприятных - дохли :). Кроме того, по игровому полю медленно летал шар-телепорт. Попавшая в него зверюшка телепортировалась случайным образом на другого клиента (опять ремотинг!). Побеждал тот, чья популяция становилась максимальной. В общем, прекрасная штука для знакомства с .Net. Помнится тогда и в категории хищников и у категории травоядных среди победителей были ребята из Питера (они применили свои наработки в теории конечных автоматов).

Мне же Terrarium, запомнилась тем, что с ее помощью я изучил .Net, фактически круто сменил свою специализацию и устроился на работу .Net разработчиком, имея за плечами в области .Net лишь опыт программирования в Terrarium :)
Такая вот история.

вторник, июля 15, 2008

Как работает ManualResetEvent

Ну вот я вернулся из отпуска и после долгого молчания решил разразиться техническим постом.
Меня уже несколько раз приходилось объяснять принцип работы и использования ManualResetEvent. И вот я решил все это подробно разжевать с примерами кода.
Продвинутых коллег сразу предупреждаю, ничего нового и эксклюзивного здесь не будет, если ManualResetEvent для вас не является белым пятном, то дальше можете не читать.

Итак, когда наш код выполняется в одном потоке, инструкции выполняются в определенном программой порядке. Но стоит нам запустить код в нескольких потоках у нас сразу возникает неопределенность в каком порядке выполняются инструкции нашего кода. Рассмотрим простейшим пример.


static void Main(string[] args)
{
Thread thread = new Thread(new ThreadStart(ParallelWork));
thread.Start();
Console.WriteLine("Main:thread work finished");
}

static void ParallelWork()
{
Console.WriteLine("Thread:begin work");
Thread.SpinWait(1000);// что то полезное делаем тут
Console.WriteLine("Thread:end work");
}


В методе Main() мы создаем и запускаем поток, в котором будет выполнен код метода ParallelWork(). Запустив несколько раз этот код мы можем получить на выходе:
Thread:begin work
Thread:end work
Main:thread work finished

или
Thread:begin work
Main:thread work finished
Thread:end work

или
Main:thread work finished
Thread:begin work
Thread:end work

Обычно ничего страшного в этом нет, ведь параллельное исполнение кода именно для этого и задумывалось. Но иногда у нас возникает необходимость синхронизировать исполнение отдельных участков кода в разных потоках.
Если взять наш куцый примерчик, то предположим, мы хотим, чтобы метод Main дождался завершения метода ParallelWork, и только потом завершился сам. Достичь этого можно разными способами. Например, можно объявить булеву переменную флаг, в метод Main вставить цикл проверки этого флага, а в конце метода ParallelWork выставлять флаг в true. Вот так:


static bool flag = false;
static void Main(string[] args)
{
Thread thread = new Thread(new ThreadStart(ParallelWork));
thread.Start();
while (!flag) ;
Console.WriteLine("Main:thread work finished");
}

static void ParallelWork()
{
Console.WriteLine("Thread:begin work");
Thread.SpinWait(1000);// что то полезное делаем тут
Console.WriteLine("Thread:end work");
flag = true;
}


На выходе получим искомое:

Thread:begin work
Thread:end work
Main:thread work finished


за счет того что в методе Main будет молотить холостой цикл while(!flag) ; пока метод ParallelWork() не выставит flag = true. Основной недостаток такого способа синхронизации состоит в том, что холостой цикл очень хорошо грузит процессор.

Ту же самую задачу можно решить при помощи ManualResetEvent.
Вот так:


static ManualResetEvent sync;
static void Main(string[] args)
{
sync = new ManualResetEvent(false);
Thread thread = new Thread(new ThreadStart(ParallelWork));
thread.Start();
sync.WaitOne();
Console.WriteLine("Main:thread work finished");
}

static void ParallelWork()
{
Console.WriteLine("Thread:begin work");
Thread.SpinWait(1000);// что то полезное делаем тут
Console.WriteLine("Thread:end work");
sync.Set();
}


Как это работает? Очень просто.
У ManualResetEvent есть внутреннее состояние: сигнальное и несигнальное. В сигнальное он переводится методом Set() в несигнальное - методом Reset(). Также начальное состояние ManualResetEvent можно задать и в конструкторе. В нашем случае мы создаем экземпляр ManualResetEvent в несигнельном состоянии.

Самый главный его метод WaitOne(). Когда в коде встречается вызов WaitOne() исполнение приостанавливается, если ManualResetEvent в несигнальном состоянии. А если в сигнальном – WaitOne исполняется без задержки. На этом и основано его использование. Мы заставляем код в главном потоке остановиться и ждать на вызове WaitOne(), пока где нибудь в другом потоке не вызовут Set(). Вызов Set() как-бы подает сигнал другому потоку (или нескольким), который висит и ждет на вызове WaitOne(), после чего этот поток может продолжить свое исполнение. Т.е. WaitOne() по своему эффекту похож на Thread.Sleep(), но с возможностью разбудить поток в нужный момент из другого потока.

Благодаря тому, что ManualResetEvent использует synchronization handle операционной системы, поток ожидающий на вызове WaitOne() действительно приостанавливается, ему не выделяются кванты процессорного времени и он не тормозит исполнение других потоков. И это основное отличие от синхронизации с флагом и холостым циклом. Висеть на WaitOne() может не один а несколько потоков, и все они выдут из ожидания при вызове одного Set().

Напоследок, хочу упомянуть, что рассматриваемую в примере задачу синхронизации можно решить и без ManualResetEvent, при помощи Thread.Join(), однако стоить заметить, что Thread.Join() использует внутри те же механизмы, что и ManualResetEvent


static void Main(string[] args)
{
sync = new ManualResetEvent(false);
Thread thread = new Thread(new ThreadStart(ParallelWork));
thread.Start();
thread.Join();
Console.WriteLine("Main:thread work finished");
}

static void ParallelWork()
{
Console.WriteLine("Thread:begin work");
Thread.SpinWait(1000);// что то полезное делаем тут
Console.WriteLine("Thread:end work");
}