Показаны сообщения с ярлыком Enterprise lib. Показать все сообщения
Показаны сообщения с ярлыком Enterprise lib. Показать все сообщения

понедельник, 19 мая 2008 г.

Enterprise Library 4 final release with Unity integration

Вышел финальный релиз Enterprise Libary 4.0, который интегрирован с IoC framework Unity (в предыдущем релизе он не использовался поумолчанию) http://blogs.msdn.com/agile/archive/2008/05/16/enterprise-library-4-0-for-visual-studio-2008-released.aspx. Unity же был обновлен до версии 1.1. http://blogs.msdn.com/agile/archive/2008/05/16/unity-refresh-v1-1.aspx

В Enterprise Library сохранены все интерфейсы предыдущего релиза (3.1), проведена оптимизация (особенно в Logging AB), поддержка WMI 2.0, интеграция с Unity (см. выше), багфиксинг.

Удачи.

среда, 30 апреля 2008 г.

Enterprise Logging System. Part 3. Service.

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


У сервиса 2 обязанности:
  • централизовать хранение настроек;
  • помещать сообщения в сконфигурированное хранилище.
соответственно выглядит его контракт:


Сообщения несут разные наборы информации, и, чтобы не перегружать базовый класс сообщения неиспользуемыми свойствами, я ввел в data contract иерархию.

Для того, чтобы сервис мог десериализовать сообщения классов-наследников базовый класс необходимо пометить атрибутами [KnownType(typeof(...))], где ... - имена классов наследников.

Metadata.

Первая задача предельно проста. Необходимо сохранять и извлекать настройки клиентов из БД. Если настроек для конкретного клиента нет, то возвращаются дефолтные настройки, общие для всех клиентов.
Метаданные хранятся в БД и извлекаются при помощи Linq2Sql классов.

Вот классы, являющиеся метаданными системы:


Клиент идентифицируется по запросу, из полей которого составляется уникальный хеш. По нему то и ведется поиск в базе.






Logs
Сохранение сообщений делегируется адаптеру системы логирования, который подключается к сервису в файле конфигурации хоста сервиса. Я писал об этом здесь: http://bobbbloggg.blogspot.com/2008/04/loosely-coupled-wcf-service.html. В системе поумолчанию используется адаптер Enterprise Library Logging AB. Так как логи должны сохраняться в надежном хранилище с возможностью репликации со всеми данными системы на основной сервер, то конечно же в роли этого хранилища выступает БД. Дабы не тащить за собой Data Access AB из Enterprise Library, т.к. мне совсем не нравится его реализация, я написал свой легковесный custom DBTraceListener, сохраняющий логи в БД при помощи класов Linq2Sql. Этот TraceListener подключается к Logging AB в файле конфигурации и легко может быть заменен на любой другой.

Вот структура таблиц логов в БД.


Linq2Sql поддерживает паттерн отображения наследования с одной таблицой (см. Фаулер). Поэтому в таблицу введено специальное поле дискриминатор (DiscKey) и linq2sql классы образуют повторяющую LogMessage иерархию.


Все сообщения разделены на категории по уровню (LogLevel). Также в системе по умолчанию настроена одна дополнительная "специальная" категория сообщений - CriticalErrors, к которой подключен EmailTraceListener, который при деплойменте может быть настроен на почтовый ящик администратора системы и в случае получения сообщений о критических ошибках он будет получать письмо по почте, с тем чтобы привлечь его внимание к проблеме. Распределением сообщений по категориям занимается LoggingSystemAdapter.


В следующий раз я опишу фасад системы.
Удачи.

понедельник, 21 апреля 2008 г.

Enterprise Logging system. Part1 Architecture.

Имею желание поделиться с вами своим взглядом на дизайн системы логирования масштаба предприятия.

"О чем этот сумасшедший?"

Я о той части SOA инфраструктуры, которая отвечает за логирование событий в enterprise. С началом разработки данной системы мы начинаем лавировать в сторону настоящей SOA инфраструктуры, малыми галсами приближаясь к нашему видению такой модной нынче SOA концепции как ESB.

"Точно двинутый, ничего не понятно же".

Да блин, все просто - нужна система, в которую централизовано будут сваливать свои логи все приложения, работающие в сети одной компании.

Итак задачи, ограничения и требования к данной системе:


  • должна быть предельно проста в применении разработчиками;
  • должна функционировать в распределенной системе - конечная точка сервис;
  • возможность работать в отключенном режиме;
  • нельзя расчитывать на то, что на клиенте будет установлен SQL сервер, поэтому должна быть возможность подключать различные стратегии кеширования;
  • возможность подключения различных библиотек логирования;
  • возможность настройки уровня, категорий и пр. данных сообщений, которые будут отправляться с клиента;
  • сохранение логов в БД сервера, с тем чтобы реплицировать их с остальными данными на основной сервер;
  • возможность настройки альтернативного хранилища/цели сообщений логов (e-mail, event log…) (при помощи сторонних библиотек логирования);
  • возможность передать некий контекст;
  • управление исходящим траффиком
  • ...

Теперь обратимся к литературе. На codeproject я нашел несколько статей, в которых описано решение подобных задач:

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

В качестве стронней библиотеки логирования я рассматривал следующие проекты:

Функциональность и модель программирования во всех этих проектах примерно одинаковая и включает в себя возможность логировать в такие хранилища как Event log, база данных, E-mail, и т.д. и т.п. В конечном итоге выбор я остановился на Enterprise library, во-первых потому что я уже имею опыт работы с Enterprise Library и использовал Validation Application Block в слое бизнес объектов в проектах (писал об этом здесь), а во-вторых чтобы сохранить единую базу, на которой строятся все проекты.

После обработки материалов и требований у меня сложилась следующая архитектура.


Характерными особенностями ее являются:
  • логирование всегда ведется на сервере, поэтому сервис логирования является основной компонентой системы;
  • сервис слабо связан с непосредственным кодом логирования и библиотекой, берущей на себя эти функции при помощи уже описанного мною приема http://bobbbloggg.blogspot.com/2008/04/loosely-coupled-wcf-service.html; Это оставляет за мной возможность безболезненно сменить библиотеку логирования, в случае, если звезды изменят свое расположение и я передумаю использовать Logging AB;
  • клиент работает с библотекой через фасад, а всю работу по конфигурированию, кешированию и работе с сервисом система берет на себя прозрачно для клиента, при этом по возможности не блокируя его;
  • сервис логирования - WCF singleton-сервис; Его функция централизовывать хранение настроек и делегировать сохранение сообщений адаптеру библиотеки логирования;
  • сохранение логов в базу данных производится через подключаемый к Logging AB Custom TraceListener.

Сообщения в системе имеют несколько уровней, которые в порядке приоритета распологаются в следующим образом:

  • Trace;
  • Info;
  • Warn;
  • Error;
  • Status;

Настройки клиентов, уникально себя идентифицирующих, хранятся в БД. При первом обращении к фасаду системы, клиент обращается к сервису с просьбой передать настройки, если сервис недоступен - пытается "поднять" их из локального конфига.

Локальные стратегии кеширования подключаются к клиентской части системы при помощи модели провайдеров ASP.NET. Так же система расширяется за счет подключаемых фильтров, которые контролируют исходящий траффик. По умолчанию в систему включены провайдер локальных стратегий, стратегия кеширования в оперативной памяти (для тестирования), стратегия для кеширования в IsolatedStorage, фильтр траффика по уровню сообщения.

В серверной части система расширяется при помощи модели расширений WCF, подключая к сервису адаптер, которому делегируется работа с библиотекой логирования. В систему встроен адаптер для работы с EnterpriseLibrary.

Всю работу по отправке сообщений в сервис берет на себя компонента системы Disconnected Service Agent (DSA). В Smart Client Software Library есть Application Block, реализующий данную функциональность, но по упомянутым выше причинам я предпочел не использовать готовую функциональность, а написал свой небольшой блок, опять же почерпнув из MS-ской реализации светлые идеи. Основными частями данного блока являются:

  • Connection Monitor - агент, который следит за состоянием подключения к сервису;
  • Request Manager - диспетчер запросов. Ставит запросы в очередь (локальную стратегию кеширования), пытается асинхронно отправить их, дабы не блокировать клиента;
  • Local Store Strategy - локальная стратегия кеширования, которая подключается в процессе конфигурирования.


На этом сегодня все в следующий раз подробнее о сервисе.

Удачи.

вторник, 1 апреля 2008 г.

Enterprise Library 4

Вышел CTP Enterprise Library.
линк для скачивания: http://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=entlib&ReleaseId=12142

Удачи.

вторник, 4 марта 2008 г.

Business objects validation and UI integration

Случилось мне писать WPF Wizard. И было это сложно и интересно и много проблем порешал я.


Вот одна из них довольно интересная - валидация бизнес-объектов и управление состоянием UI в зависимости от результата валидации.

В прошлых своих постах я писал про паттерн проектирования и разработки клиентских UI приложений DM-V-VM. В моей реализации UI никак не следит за валидностью нижележащих данных, т.к. в архитектуре системы заложено, что редактированием бизнес-объектов занимается специально предназначенный для этого UI - wizard, а все остальные представления занимаются лишь отображением данных без возможности редактирования.

Если есть необходимость редактировать данные вне wizard'а, то здесь описан подход, как это можно реализовать.

Итак, что же нам нужно и что мы имеем?

Нужно:
  • автоматическое отслеживание представлениями состояния валидности объекта (кнопки Next, Save и т.п. должны быть отключены, если редактируемый объект в невалидном состоянии, и наоборот. Проще говоря, wizard не должен дать вам совершить ошибку);
  • прозрачное для разработчика валидирование - это не должна быть его забота и он не должен писать в code-behind никакой логики валидирования;

Что есть:

  • В WPF binding engine есть валидация. Она реализуется при помощи объектов классов наследников ValidationRule и статического класса Validation. Почему бы не использовать их и не угомониться на этом? Да потому что это валидация данных, лежащих в UI контролах, а не валидация бизнес объектов. Нужен унифицированный механизм, не завязанный на UI технологию, т.к. валидровать объект можно не только в UI. Но эту технологию можно использовать как точку расширения и подключения дополнительной логики в процессе валидирования данных wizard.
  • В Enterprise library (EL) есть Validation Application Block (VAB), который предназначен для решения задачи валидирования бизнес-объектов;
  • В предыдущих постах я писал об использовании комманд WPF, управляющих состоянием UI.
Итак, кажется все, что нужно есть, тогда приступим.

Для начала сценарий: wizard редактирует сущность посетитель/пользователь (как угодно).

  1. на первой странице заполняются ФИО, причем имя или фамилия поля обязательные.
  2. на второй странице заполняются настройки доступа - логин, пароль, подтверждение пароля
  3. на третьей странице заполняется и так далее... =)
В разработанном мною wizard'e редактируемая сущность "вешается" wizard'у на DataContext, таким образом все его визуальное дерево (страницы с контролами в них) наследует это свойство.

Каждому UI контролу, выполняющему какое-нибудь действие присваивается wpf команда. Каждая wpf команда, обернута уже знакомым нам классом CommandModel. Эти классы реализуют логику принятия решения о состоянии команды и логику выполнения команды. Делегируем этим классам валидирование бизнес-объекта. Но как она реализована?

Предоставим бизнес-объектам самим реализовывать свою логику валидирования. Для этого используем EL VAB. У David'а Hayden'а есть отличная серия статей по VAB. Валидация при помощи VAB настраивается 2мя путями, либо программно (при помощи аттрибутов), либо административно (при помощи конфигурационного файла). В нашем случае мы будем использовать аттрибуты, т.к. наши ограничения не меняются и заранее известны. Управлять подмножеством свойств для валидации можно при помощи ValidationRuleSet - идентификатора подмножества валидаторов. Так реализуется частичное валидирование. Т.к. страницы wizard'а редактируют только части объекта, то очевидно страница wizard'а - наилучшее место для размещения свойства validationRuleSet.

Теперь по ограничениям, т.к. наше приложение service ориентировано, то бизнес-объекты будут поставляться WCF сервисами и следовательно должны быть помечены атрибутами [DataContract] или хотя бы [Serializable] для того, чтобы сервис мог передать их через канал и десереализовать. Валидирование реализуем при помощи паттерна Template method (метод DoValidate() ), реализацию которого возложим на класс-наследник.


Диаграмма иерархии наследования классов домена.

На диаграмме приведена иерархия классов домена:


  1. ObservableObject - уже встречавшийся нам класс, реализующий InotifyPropertyChanged.
  2. DomainObject - базовый класс домена. Содержит шаблонный метод DoValidate, в котором наследниками должна будет реализовываться логика валидирования. Не является data contract, но помечен аттрибутом [Serializable]. Такое решение принято чтобы не вводить зависимость домена от технологии создания сервисов (WCF).
  3. DomainDTO - базовый класс для всех дата контрактов. Знает обо всех своих наследниках (при помощи аттрибута [KnownType]).
  4. DomainDTO, где Т - тип класса наследника. Реализует логику валидирования объекта.

Базовый класс домена.

Как видите класс не сериализует результаты валидирования, ибо нет нужды передавать их через границу сервиса.

Базовый дата контракт, реализующий шаблоный метод.

DomainDTO в статическом конструкторе собирает аттрибуты-валидаторы и сохраняет их. Точно также как базовый класс домена не включает в data contract список валидаторов, т.к. нет нужды передавать их через границу сервиса. Data contract'ы на стороне сервиса и клиента используются одни и те же, так что нет нужды импортировать wsdl сервиса и генерить классы контрактов, а потом вручную добавлять в них ту же логику.



Когда DomainDTO просят - валидирует себя по указанному подмножеству валидаторов (ruleSet).

Здесь он просит фасад VAB фабрики объектов создать композитный валидатор по указанному ruleSet подмножеству, а затем передает себя валидатору, который собирает, комбинирует и возвращает ответ от всех валидаторов, входящих в указанный validationRuleSet.

Наш класс посетителя мог бы выглядеть вот так:


В нем свойство Name помечено валидатором длины строки (от 1 до 200) с идентификатором подмножества CommanRuleSet, так что у страницы, редактирующей это же свойство ValidationRuleSet должно быть присвоено CommanRuleSet.

Итак, с валидацией мы разобрались, теперь можно писать логику команд.

В wizard'е есть следующие команды:


  • NextPage - переход на следующую страницу;
  • BackPage - переход на предыдущую страницу;
  • GoToPage - переход на указаную страницу;
  • Save - сохранить редактируемый объект;
  • Cancel - отменить редактирование объекта;
  • Help - показать файл справки;
Решение проиллюстрируем при помощи команды NextPage. В OnQueryEnabled команда проверяет валиден ли объект по подмножеству validationRuleSet, заданном для текущей страницы wizard'а, не является ли текущая страница последней и не помечена ли текущая страница классом Validation как невалидная (именно это является точкой расширения, т.к. разработчик конкретного wizard'а в UI может добавить логику валидирования и сообщить команде, что по его мнению редактируемые данные невалидны):


Эта логика будет автоматически вызываться WPF framework и состояние редактируемого объекта будет управлять состоянием кнопки Next.

Extensibility point.

Для демонстрации этой возможности представьте себе следующий сценарий:

на странице wizard'а редактируется объект пользовательских данных для разграничения доступа (логин, пароль). На странице есть 1 TextBox - Login, и 2 PasswordBox'а - Password, PasswordConfirmation. В DataContext страницы wizard'а лежит бизнес-объект (пусть LoginCredential). Поле Text textbox'а Login привязано (binding) к свойству LoginCredential.Login, поле Text passwordbox'а Password в codebehind привязно к LoginCredential.Password. Для валидации данных, необходимо, чтобы пользователь повторил пароль, но в объекте LoginCredential нет поля повтор пароля. Если в passwordbox PasswordConfirmation не будет введен идентичный первому пароль все навигационные кнопки не должны быть активными. Как быть? Мы по-прежнему не хотим писать spaghetti-code.

Здесь и вступает в игру binding validation, ведь теперь мы валидируем не бизнес объект, а данные, которые лежат в UI, но сильно влияют на принятие решения о валидности объекта.

Для облегчения интеграции внешней логики в процесс валидации я написал класс ValidationHelper.



У класса есть attached-свойство Sink, которым можно пометить любой визуальный объект, наследованный от FrameworkElement и у которого задано свойство Name. В своем дефолтном стиле wizardPage присваивает привязывает ValidationHelper.Sink к себе самой, тем самым помечая себя для валидации.


Также у класса есть attached-свойство Bag, которое будет очень полезно объектам у которых нет dependency-свойств, чтобы привязывать какие-либо данные через WPF binding.

Далее, в нашем сценарии, разработчику необходимо привязать ValidationHelper.Bag у первого объекта PasswordBox к LoginCredential.Password, т.к. свойство Password PasswordBox'а не является dependency-свойством (странное решение).


Здесь вы видите в binding в коллекцию ValidationRules добавляется PasswordValidationRule, которое и является ключевым элементом интеграции. Из code-behind'а (именно, опять же из-за того что Password свойство обычное, а не dependency), при изменении пароля, получаем объект BindingExpression и просим его обновить Source, тем самым вызывая валидацию.


Сложным моментом здесь является передача объекту ValidationRule значений из UI, ведь ValidationRule не наследуется от DependencyObject и не может использовать binding. Решение этой проблемы нашел и описал наш любимы rock-star Josh Smith здесь. В кратце опишу суть - поскольку мы не можем унаследоваться от DependencyObject создадим суррогатный объект наследник DependencyObject, с единственным Dependency-свойством Value. Нужное нам значение из визуального дерева мы пересылаем в ресурсы объекту, который Josh очень лаконично назвал DataContextBridge, при помощи особого вида binding - OneWayToSource. А потом привязываем Value суррогатного объекта к DataContextBridge при помощи обычного binding. В нашей реализации таким образом объекту PasswordValidationRule передаются пароль и ссылка на страницу, в которой происходит редактирование.

Далее обязанность нашего правила сравнить пароли и пометить соответствующим образом страницу:



Логика очень проста - если пароли различны, то при помощи вспомогательного метода ValidationHelper.MarkInvalid страница помечается невалидной и в OnQueryEnabled будет принято решение о невалидности данных,т.к. страница невалидна. Если же введенные пароли идентичны, то страница помечается валидной вспомогательным методом ValidationHelper.ClearInvalid.
ValidationHelper.MarkInvalid и ValidationHelper.ClearInvalid являются всего лишь обертками методов класса System.Windows.Controls.Validation MarkInvalid и ClearInvalid, передавая им BindingExpression объекта на свойство Validation.Sink.
Совсем непростая реализация, но позволяет добиться ожидаемого поведения.
Также wizard предлагает более простую модель - реакция на события смены страниц, но это решение не обеспечивает нужного поведения UI элементов.

Удачи.
PS: перечитал пост и понял, что в последней части можно было бы обойтись и без столь сложного кода с ValidationRule - просто в code-behind сравнивать строки и помечать страницу валидной либо невалидной. подумал но исправлять ничего не стал - пусть послужит демонстрацией продвинутых возможностей WPF. Возможно где-нибудь в другом более сложном сценарии вам пригодится этот код. И обратите внимание на VirtualBranch прием от Josh'а.
PPS: а потом еще раз подумал и решил, что все правильно и так и надо делать, ибо при валидации в ValidationRule WPF автоматически отобразит результаты валидации в UI, надо лишь задать ErrorTemplate в классе Validation.