Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
Aml Pages - это электронная записная книжка, оперативный блокнот на все случаи жизни, каталогизатор документов. Позволяе...
McAfee Stinger - это бесплатный, не требующий установки антивирус, для обнаружения и удаления с компьютера известных вир...
Удобная программа для работы с музыкальными коллекциями. Она понимает самые распространенные виды музыкальных форматов, ...
Программа для управления драйверами, которая позволяет полностью удалить драйвер из системы. Driver Fusion позволяет уда...
Программа для видеочатов высокого качества, обмена сообщениями и дешевыми звонками на телефоны в любую страну....
OSzone.net Microsoft Разработка приложений .NET Framework Перенос существующих .NET-библиотек на современные платформы RSS

Перенос существующих .NET-библиотек на современные платформы

Текущий рейтинг: 0 (проголосовало 0)
 Посетителей: 608 | Просмотров: 734 (сегодня 0)  Шрифт: - +

Одна из самых сильных сторон Microsoft .NET Framework — многообразие сторонних библиотек (как с открытым исходным кодом, так и коммерческих) для этой платформы. Это доказательство зрелости экосистемы .NET-разработки, где есть не только отличные API, которые можно выбирать в самой .NET Framework, но и тысячи сторонних библиотек для обслуживания HTTP-запросов, рисования сеток в настольных приложениях, хранения структурированных данных в файловой системе и многого другого. И действительно, даже при быстром просмотре популярных хранилищ .NET-кода вы обнаружите более 32 000 проектов на CodePlex, более 5000 образцов кода на code.msdn.microsoft.com и более 10 000 уникальных пакетов в галерее NuGet Gallery!

Появление новых программных платформ, например Windows Phone 8 и Windows 8, потенциально способно вдохнуть новую жизнь в эти проверенные временем кодовые базы. .NET-библиотеки, которые верой и правдой служили вам многие годы в настольных и серверных приложениях, могут оказаться не менее полезными (а иногда и более полезными) в новых средах — при условии, что вы готовы приложить усилия для их перевода под эти новые платформы. Традиционно такие задачи были трудными и утомительными, но при должном внимании и тщательном планировании вам может значительно помочь Visual Studio 2012, имеющий несколько средств, которые минимизируют потенциальные трудности и максимально расширяют возможности повторного использования подобных библиотек на различных платформах.

В этой статье я исследую проблемы, обнаруженные при реальном переносе проекта с открытым исходным кодом Sterling — объектно-ориентированной базы данных (OODB) NoSQL. Я дам краткий обзор этой библиотеки, расскажу о проблемах при ее переносе и решениях для их преодоления, а в заключение рассмотрю некоторые шаблоны и рекомендации, которые помогут вам при переносе своей библиотеки.

Что такое Sterling?

Sterling — это облегченная библиотека хранилища данных NoSQL, которая обеспечивает быстрое извлечение из базы данных экземпляров .NET-классов по индексам. Также поддерживаются операции обновления, удаления, резервного копирования и восстановления, отсечения и прочие действия, хотя, как и другие технологии NoSQL, она не предоставляет универсального механизма запросов на основе SQL-языка. Вместо этого понятие запроса включает набор упорядоченных операций:

  • сначала извлекается набор предопределенных ключей или индексов, сопоставленных с экземплярами классов с отложенной загрузкой (lazy-loaded class instances);
  • потом выполняется индексация набора ключей для осуществления начальной быстрой фильтрации всего возможного набора результатов;
  • наконец, используются стандартные запросы LINQ to Objects к уже отфильтрованным парам «ключ-значение» для дальнейшего «просеивания» результатов.

Очевидно, что модель использования Sterling (и аналогичных средств для NoSQL-баз данных) отличается от таковой в традиционных реляционных базах данных вроде SQL Server. Отсутствие формализованного языка запросов кажется новичкам особенно странным. По сути, это считается сторонниками NoSQL одной из сильных сторон, учитывая потенциальную сложность и издержки, связанные с сопоставлением входов и выходов между миром запросов и миром кода. В таком NoSQL-решении, как Sterling, никаких сопоставлений нет, так как запросы и код являются неделимым целым.

Полное описание основ Sterling выходит за рамки моей статьи (подробности см. в статье Джереми Ликнесса [Jeremy Likness] «Sterling for Isolated Storage on Windows Phone 7» по ссылке msdn.microsoft.com/magazine/hh205658), но я выделю основные ключевые преимущества и недостатки, которые следует учитывать:

  • библиотека имеет малый объем (около 150 Кб на диске) и отлично подходит для хостинга внутри процесса;
  • изначально работает со стандартным набором сериализуемых .NET-типов;
  • набор концепций, необходимых для использования базовой функциональности, невелик; вы можете приступить к работе со Sterling, написав всего пять строк кода на C#;
  • традиционные средства баз данных, такие как гранулярная защита, каскадные обновления и удаления, настраиваемая семантика блокировки, гарантии атомарности, целостности, изоляции, надежности (ACID) и прочее в Sterling не поддерживаются. Если вам нужны эти средства, подумайте об использовании полнофункциональной реляционной СУБД вроде SQL Server.

Автор библиотеки Sterling (мой коллега по Wintellect, Джереми Ликнесс) исходил из того, что она предназначается для нескольких платформ; он создал двоичные файлы для .NET Framework 4, Silverlight 4 и 5, а также Windows Phone 7. Поэтому, размышляя о том, какая работа потребуется для обновления Sterling под .NET Framework 4.5, Windows Phone 8 и приложения Windows Store, я знал, что архитектура позволит это сделать, но не знал точно, что повлечет за собой этот проект.

Рекомендации, даваемые мной в этой статье, являются прямым результатом моего опыта в обновлении Sterling под платформы .NET Framework 4.5, Windows Phone 8 и приложения Windows Store. Хотя некоторые детали специфичны для проекта Sterling, многие другие распространяются на широкий спектр различных проектов в экосистеме Microsoft.

Проблемы и решения

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

Приспосабливание к архитектурам с разной философией Первый набор потенциальных проблем является в какой-то мере философским по своей природе, хотя реально влияет на общие усилия в процессе переноса. Задайте себе вопрос: «В какой степени архитектура и дизайн библиотеки, которую я хочу перенести, соответствуют распространенным шаблонам и моделям использования, принятым на новых целевых платформах?».

На этот вопрос не так-то легко ответить, и единственного универсального ответа на него нет. Может оказаться так, что ваш «интеллектуальный» диспетчер разметки Windows Forms будет трудно или вообще невозможно перенести в Windows Presentation Foundation (WPF). Эти API явно различаются, но в конечном итоге вам подставят подножку именно различия в философиях базовых архитектур и концепциях управления и размещения UI-элементов. Как еще один пример, собственные UI-элементы ввода, отлично работающие в классических сценариях с использованием для ввода клавиатуры и мыши, могут стать весьма плохим решением в средах с поддержкой сенсорного ввода, например в Windows Phone или Windows 8. Одного желания перенести кодовую базу недостаточно — обязательно должна быть совместимость нижележащих архитектур между старой и новой платформами и сильное желание нивелировать любые незначительные различия, которые наверняка имеются. В случае Sterling мне пришлось преодолевать несколько таких дилемм.

Самой заметной архитектурной проблемой было расхождение между синхронным API обновления данных в Sterling и ожидаемой асинхронной природой такого поведения в библиотеках, ориентированных на Windows Phone 8 и приложения Windows Store. Sterling проектировалась несколько лет назад в мире, в котором асинхронные API были редкостью, а инструментарий и методики их создания находились в лучшем случае в зачаточном состоянии.

Ожидания пользователей в отношении отзывчивости программных решений кардинально выросли за последние несколько лет.

Вот типичная сигнатура метода Save в Sterling до переноса:

object Save<T>(T instance) where T : class, new()

Здесь важно отметить, что этот метод выполняется синхронно, т. е. независимо от того, сколько времени уйдет на сохранение аргумента экземпляра, вызвавший код будет блокирован в ожидании завершения этого метода. Это может вызвать обычный набор проблем из-за блокировки потока: неотзывчивые UI, гораздо меньший уровень масштабируемости сервера и т. д.

Ожидания пользователей в отношении отзывчивости программных решений кардинально выросли за последние несколько лет; никто из нас не хочет мириться с UI, замирающим на несколько секунд из-за ожидания окончания операции сохранения. Отвечая на эти требования, руководства по проектированию API для новых платформ, таких как Windows Phone 8 и Windows 8, обязывают делать открытые библиотечные методы вроде Save только асинхронными, не блокирующими операциями. К счастью, теперь этого добиться гораздо легче благодаря таким средствам, как .NET-модели программирования Task-based Asynchronous Pattern (TAP) и ключевым словам async и await в C#. Вот обновленная сигнатура Save:

Task<object> SaveAsync<T>(T instance) where T : class, new()

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

Вы должны отдавать себе отчет в том, что здесь показаны лишь сигнатуры методов; преобразование синхронной реализации в асинхронную требует дополнительного рефакторинга и перехода на API асинхронной работы с файлами для каждой целевой платформы. Например, синхронная реализация Save использовала для записи в файловую систему BinaryWriter:

using ( BinaryWriter instanceFile = _fileHelper.GetWriter( instancePath ) )
{
  instanceFile.Write( bytes );
}

lНо, поскольку BinaryWriter не поддерживает асинхронную семантику, я перешел на асинхронные API, подходящие для каждой целевой платформы. Так, на рис. 1 показано, как выглядит SaveAsync для драйвера Sterling Windows Azure Table Storage.

Рис. 1. SaveAsync для драйвера Sterling Windows Azure Table Storage

using ( var stream = new MemoryStream() )
{
  using ( var writer = new BinaryWriter( stream ) )
  {
    action( writer );
  }
  stream.Position = 0;
  var entity = new DynamicTableEntity( partitionKey, rowKey )
  {
    Properties = new Dictionary<string, EntityProperty>
    {
      { "blob", new EntityProperty( stream.GetBuffer() ) }
    }
  };
  var operation = TableOperation.InsertOrReplace( entity );
  await Task<TableResult>.Factory.FromAsync(
    table.BeginExecute, table.EndExecute, operation, null );
}

Я по-прежнему использую BinaryWriter для записи дискретных значений в поток данных в памяти, но потом обращаюсь к Windows Azure DynamicTableEntity и паре CloudTable.BeginExecute/EndExecute для асинхронного сохранения содержимого байтового массива потока данных в Windows Azure Table Storage. Также потребовалось внести еще несколько аналогичных изменений, чтобы добиться асинхронного обновления данных библиотекой Sterling. Главный момент: рефакторинг API, открытых пользователю, может быть лишь первыми несколькими шагами, необходимыми для перепроектирования с целью последующего переноса. Планируйте свою работу и соответственно оценивайте усилия, которые потребуются для ее выполнения. И будьте реалистами: возможно, каких-то целей не удастся добиться с приемлемыми затратами.

По сути, мой опыт со Sterling как раз и выявил одну из таких нереалистичных целей. Основы архитектуры Sterling заключаются в том, что все операции с хранилищем выполняются с применением строго типизированных данных, используя стандартный контракт данных в .NET по API сериализации и по расширениям. Это прекрасно работает для Windows Phone и клиентов .NET 4.5, а также для приложений Windows Store, написанных на C#. Однако в мире HTML5 and JavaScript нет понятия строгой типизации. После некоторых исследований и обсуждений с Ликнессом я осознал, что простого способа сделать Sterling доступным таким клиентам нет, поэтому предпочел вычеркнуть их из списка поддерживаемых вариантов. Разумеется, такие потенциально возможные препятствия следует рассматривать на индивидуальной основе, но вы должны знать, что можете столкнуться с ними, и быть реалистами.

Используйте общий код для целевых платформ Следующая крупная проблема, возникшая у меня, была одной из тех, с которыми мы все рано или поздно сталкиваемся: как использовать общий код в нескольких проектах, ориентированных на разные платформы?

Выявление и использование общего кода между проектами — проверенная временем стратегия, которая минимизирует время выпуска продукта на рынок и существенно упрощает сопровождение этих проектов. Мы годами делаем это в .NET; типичный шаблон — определение сборки Common и ссылка на нее из нескольких проектов. Другой излюбленный прием — функциональность «Add As Link» в Visual Studio, которая дает возможность разделять один мастер-файл исходного кода между несколькими проектами, как показано на рис. 2.

*
Рис. 2. Функциональность Add As Link в Visual Studio 2012

Даже сегодня эти варианты отлично работают, если все потребительские проекты нацелены на одну и ту же платформу. Однако, когда нужно предоставить общую функциональность на нескольких платформах (как в случае Sterling), создание единой сборки Common для такого кода становится довольно тяжелой ношей в разработке. Создание и сопровождение нескольких мишеней сборки (build targets) становится необходимостью, что увеличивает сложность конфигурации проекта и процесса сборки. Использование препроцессорных директив (#if, #endif и др.) для условного включения поведения, специфичного для конкретной платформы, в определенных конфигурациях сборки является мнимой необходимостью, лишь затрудняя чтение и понимание кода. В таких конфигурациях вы попусту растрачиваете свою энергию и отвлекаетесь от основной цели — решения реальных задач с помощью программного кода.

К счастью, Microsoft предвидела потребность в упрощении кросс-платформенной разработки и, начиная с .NET Framework 4, добавила новую функциональность — Portable Class Libraries (PCL). Такие PCL позволяют избирательно ориентироваться на необходимые версии .NET Framework, Silverlight и Windows Phone, а также Windows Store и Xbox 360 из одного .NET-проекта Visual Studio. При выборе шаблона проекта PCL среда Visual Studio автоматически обеспечивает, чтобы ваш код использовал только библиотеки, существующие на каждой из выбранных целевых платформ. Это исключает необходимость в громоздких препроцессорных директивах и множестве мишеней сборки. С другой стороны, это накладывает некоторые ограничения на API, которые вы можете вызывать из свой библиотеки, — вскоре я поясню, как обойти такие ограничения. Подробнее о возможностях и применении PCL см. «Cross-Platform Development with the .NET Framework» (msdn.microsoft.com/library/gg597391).

PCL естественным образом подходили для достижения кросс-платформенности в моем случае со Sterling. Я смог выполнить рефакторинг более 90% кодовой базы Sterling в одну общую PCL, которую можно использовать безо всяких модификаций из .NET Framework 4.5, Windows Phone 8 и Windows 8. Это огромное преимущество для проекта, который нужно сохранять актуальным в долгосрочной перспективе.

Краткое замечание по поводу проектов модульных тестов. На сегодняшний день для кода модульных тестов не существует никакого эквивалента PCL. Основное препятствие, мешающее его созданию, — отсутствие унифицированной инфраструктуры модульных тестов, способной работать на нескольких платформах. Учитывая эти реалии, модульные тесты Sterling я определил как отдельные проекты модульных тестов, специфичные для .NET 4.5, Windows Phone 8 и Windows 8; проект для .NET 4.5 содержит свою копию кода тестов, тогда как другие проекты разделяют код тестов по ранее упомянутой методике с применением Add As Link. Проект для каждой платформы ссылается на сборки инфраструктуры тестов, уникальной для данной платформы; к счастью, названия пространств имен и типов везде идентичны, поэтому один и тот же код компилируется без изменений во всех проектах тестов. Примеры того, как это работает, см. в обновленной кодовой базе Sterling на GitHub (bit.ly/YdUNRN).

Использование специфичных для платформы API Хотя PCL оказывают огромную помощь в создании унифицированных кросс-платформенных кодовых баз, они все же ставят вопрос: как использовать специфичные для платформы API, которые нельзя вызывать из PCL-кода? Наглядный пример — упомянутый мной рефакторинг в асинхронный код; тогда как .NET 4.5 и приложения Windows Store, в частности, предоставляют множество мощных асинхронных API, из которых есть, что выбрать, ни один из них нельзя вызывать в PCL. Можно ли попытаться совместить несовместимое?

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

Если все звучит смутно знакомым, так и должно быть: описанный здесь шаблон известен под названием «Inversion of Control» (IoC) — это проверенная временем методика проектирования ПО, обеспечивающая разделение и изоляцию модулей. Подробнее об IoC см. по ссылке bit.ly/13VBTpQ.

Занимаясь переносом Sterling, благодаря этому подходу я сумел справиться с несколькими проблемами несовместимости API. Большинство проблем с API вызывается пространством имен System.Reflection. Ирония в том, что, хотя каждая целевая платформа предоставляет всю функциональность отражения, необходимую мне для Sterling, у каждой из них есть свои нюансы, которые делают невозможной их унифицированную поддержку в PCL. Отсюда и потребность в этой методике на основе IoC. Конечную абстракцию интерфейса на C#, определенную мной для Sterling, чтобы обойти эти проблемы, см. по ссылке bit.ly/13FtFgO.

Универсальный совет

Теперь, когда я обрисовал свою стратегию переноса Sterling, я сделаю небольшой шаг назад и попытаюсь осмыслить, какие уроки, извлеченные из моего опыта, можно было бы распространить на общий случай.

Во-первых, используйте функционал PCL. PCL дают колоссальный выигрыш в кросс-платформенной разработке, и они обеспечивают достаточную гибкость конфигурации, подходящую для большинства требований. Если вы переносите существующую библиотеку (или даже пишете новую) и она ориентирована более чем на одну платформу, вы должны использовать PCL.

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

Наконец, не отказывайтесь от существующего арсенала шаблонов и методик проектирования. Я продемонстрировал, как использовать принцип IoC и встраивания зависимостей на примере Sterling, чтобы задействовать преимущества специфичных для платформы API. Другие аналогичные подходы, несомненно, тоже помогут вам. Классические проектировочные шаблоны, такие как стратегия (bit.ly/Hhms), адаптер (bit.ly/xRM3i), метод шаблона (bit.ly/OrfyT) и фасад (bit.ly/gYAK9), могут оказаться очень полезными при рефакторинге существующего кода для новых целей.

Дивный новый мир

Итог моей работы — полностью функциональная NoSQL-реализация Sterling на трех целевых платформах: .NET Framework 4.5, Windows 8 и Windows Phone 8. Приятно видеть, что Sterling работает на новейших устройствах под управлением Windows, например на моих планшете Surface и на смартфоне Nokia Lumia 920.

Проект Sterling размещен на сайте Wintellect GitHub (bit.ly/X5jmUh) и содержит полный перенесенный исходный код, а также проекты модульных тестов и проекты-примеры для каждой платформы. Он также включает реализацию модели драйвера Sterling, использующей Windows Azure Table Storage. Приглашаю вас скопировать все это с GitHub и изучить выбранные мной шаблоны и проектировочные решения, кратко обрисованные в этой статье. Надеюсь, они послужат хорошей отправной точкой в вашей работе с аналогичными целями.

И помните: не выбрасывайте старый код — переносите его на новые платформы!

Автор: Джош Лэйн  •  Иcточник: MSDN Magazine  •  Опубликована: 07.11.2013
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   .Net.


Оценить статью:
Вверх
Комментарии посетителей RSS

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