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

Идея


Первое условие к предстоящей системе — возможность динамически расширять систему без необходимости перекомпиляции отдельных модулей. Это относится как к хосту, так и к модулям.


Любое звено решения (кроме базовых интерфейсов) может быть переписано и динамически интегрировано. В довесок к возможности расширения модулей интерфейсами, хотелось иметь возможность получать динамический доступ к публичным методам, свойствам и событиям, которые доступны в любом модуле. Соответственно, все элементы класса реализующего базовый интерфейс IPlugin, которые помечены доступностью как public, должны быть видимы извне другими модулями.


Любой модуль, может изыматься и добавляться в инфраструктуру, но при этом, при решении заменить один модуль другим модулем, придётся реализовать всю функциональность удаляемого модуля. Т.е. Модули идентифицируются через атрибут AssemblyGuidAttribute, добавляемый автоматом при создании проекта. Поэтому 2 модуля с одним идентификатором не загрузятся


Каждый модуль должен быть легковесным, чтобы базовые интерфейсы не нуждались в постоянном обновлении, а при необходимости, модуль можно изъять из системы и встроить как обычную сборку в приложение через ссылку (Reference). Благо, CLR загружает зависящие сборки через ленивую загрузку (LazyLoad), так что нужда в сборках модульной инфраструктуры отпадает.


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


При этом, система должна автоматизировать рутинные задачи, которые повторяются от приложения к приложению. А именно:


  • Сохранение/загрузка пользовательских настроек или общее хранилище настроек,
  • Сохранение состояния или других параметров, в зависимости от применения,
  • Перенос ранее написанных компонентов,
  • Ограничение в использовании программного обеспечения без достаточного уровня прав (Загружать компоненты от уровня доступа, а не скрывать элементы интерфейса),
  • Взаимодействие с облачной инфраструктурой без необходимости дорабатывать логику (Message Queue, REST, SOAP сервисы, Web sockets, Caching, OAuth/OpenId/OpenId Connect...)

Решение


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


  1. Минимальные требования к основным интерфейсам,
  2. Модульная инфраструктура с независимым источником загрузки модулей,
  3. Общее хранилище настроек,
  4. Независимость решения от реализации приложений (UI, Services):
    1. Какие хосты есть на момент написания:

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


  • SAL Interfaces — Сборки с базовыми интерфейсами и интерфейсами расширений
  • Host — Приложение. (в случае использования в Visual Studio — EnvDTE Add-In), который зависит от версии запускающего приложения,
  • Plugin — В основе своей это независимый модуль (плагин) для хоста, но может зависеть от других модулей или реализовать в себе основу для группы других модулей. Кроме обычных плагинов, которые выполняют свои собственные задачи, присутствует 3 типа плагина, которые активно используются самим хостом:

    1. LoaderProvider — Провайдер, который позволяет подгружать другие модули из разных источников. Я для тестов написал загрузчик из файловой системы в память (Не работает с Managed C++), загрузкой по сети исходя из роли пользователя (Сервер написан под конкретную задачу). Но это не передел, текущая архитектура позволяет использовать в качестве источника как, к примеру, nuget.org, так и удалённое общение с хостом развёрнутым на другой машине.

    2. SettingsProvider — Провайдер, который отвечает за сохранение и загрузку настроек плагинов. Как я писал выше, по умолчанию написанные хосты используют XML для сохранения и загрузки данных, но это не ограничивает дальнейшее развитие. В готовых модулях я привёл в ккчестве примера провайдер использующий MSSQL.

    3. Kernel — Ядро бизнес-логики и массива зависимых модулей. По своей сути, является не только основой для зависимых модулей, но и идентификацией приложения для хоста (В минимуме, для идентификации в SettingsProvider, ибо в одном хосте могут запускаться разные массивы модулей, объединённые разными Kernel модулями).

Готовые базовые сборки


В результате этих требований сформировались следующие базовые сборки:


  • SAL.Core — Набор минимальных необходимых интерфейсов для хостов и модулей,
  • SAL.Windows — Зависит от SAL.Core. Набор интерфейсов для хостов и модулей, поддерживающих стандартный функционал WinForms, WPF (Form, MenuBar, StatusBar, ToolBar...) приложений,
  • SAL.Web — Зависит от SAL.Core. Набор интерфейсов для хоста и модулей, поддерживающих приложения, написанные с использованием ASP.NET (Нуждается в кардинальной доработке).
  • SAL.EnvDTE — Зависит от SAL.Windows. Предоставляет расширения для плагинов, которые могут взаимодействовать с оболочкой, на которой написана Visual Studio.

Для минимального функционирования системы, достаточно добавить ссылку на SAL.Core, а при необходимости реализовать или использовать расширения, добавить ссылку на соответствующий набор расширений интерфейсов. Либо самостоятельно расширить минимальный набор интерфейсов нужной абстракцией.


Во время запуска хоста, первым делом инициализируются встроенные в хост базовые модули, для загрузки настроек и внешних плагинов (LoaderProvider и SettingsProvider).


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


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


Данное поведение может быть переписано в хостах, если необходимо соблюсти иерархию загрузки других типов плагинов. Сейчас думаю о выносе последовательности загрузки модулей в Kernel.


Загрузка сборок


Стандартные LoaderProvider через рефлексию ищут все public классы, которые реализуют IPlugin и это не правильный подход. Дело в том, что если в коде идёт вызов конкретного класса или через рефлексию идёт обращение к конкретному классу, и этот класс не ссылается ни на какие сторонние сборки, то события AssemblyResolve не произойдёт. Т.е., сборку можно изъять из модульной инфраструктуры и использовать как обычную сборку добавив на неё ссылку и необходимость в SAL.dll отпадёт. Но базовые провайдеры модулей, реализованы по принципу сканирования текущей папки и всех объектов сборки, поэтому событие AssemblyResolve на все ссылающиеся сборки произойдёт на момент загрузки модуля.


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


В дальнейшем, как один из вариантов решения данной задачи, можно использовать сборку PEReader, которая описана ниже.


SAL.Core


Базовые интерфейсы и небольшие куски кода, реализуемые в абстрактных классах для упрощения разработки. В качестве самой минимальной версии фреймворка для основы, была выбрана версия .NET Framework v2.0. Выбор минимальной необходимой версии позволяет использовать базу на любых платформах поддерживающих эту версию фреймворка, а обратная совместимость (выбор рантайма при запуске) позволяет использовать основу до .NET Core (пока исключая).


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


На момент написания статьи единственным хостом, наследующим базовые интерфейсы, является хост для WinService приложений.


SAL.Wndows


Этот набор базовых классов, который предоставляет основу для написания приложений на основе WinForms и WPF. В составе идут интерфейсы для работы с абстрактным меню, тулбаром и окнами.



SAL.EnvDTE


С точки зрения расширения, хост как Add-In для Visual Studio расширяет интерфейсы SAL.Windows и дополняет специфичным для VS функционалом. Если зависимый плагин не находит ядра, взаимодействующего с Visual Studio, то он может продолжать работать с ограниченным функционалом.


Все написанные хосты, поддерживающие интерфейсы SAL.Core, автоматизируют следующий функционал:


  • Загрузка плагинов из текущей папки,
  • Сохранение и загрузка настроек плагинов из XML файлов в профиле пользователя,
  • Восстановление позиций и размера всех ранее закрытых окон при открытии приложения (SAL.Windows).


На этих интерфейсах реализованы следующие хосты:


  • Host MDI — Multiple Document Interface, написанный с использованием компонента DockPanel Suite,
  • Host Dialog — Диалоговых интерфейс с контрольным управлением через Windows ToolBar,
  • Host EnvDTE — Add-In для Visual Studio, проверенный на версиях EnvDTE: 8,9,10,12.
  • Host Windows Service — Хост в качестве виндового сервиса, с возможностью установки, удаления и запуска через параметры командной строки (PowerShell не поддерживается).

Логирование событий реализовано через стандартный System.Diagnostics.Trace. В хостах MDI, Dialog и WinService, listener прописанный в app.config'е, пытается отдать полученные события обратно в само приложение через Singleton, которые затем отображаются в окнах логов (Output или EventList) в зависимости от события. Для devenv.exe тоже присутствует возможность прописать trace listener в app.config'е, но в данном случае мы получим загрузку сборки хоста до загрузки его в качестве Add-In'а. Поэтому trace listener добавляется программно в коде (Отображает в VS Output ToolBar или модальным окном).


Написанная инфраструктура позволяет развиваться в направлении HTTP приложений, но для этого необходимо реализовать часть модулей, обеспечивающих как минимум аутентификацию, авторизацию и кеширования. Для приложения TTManager, которое описано ниже, был реализован свой собственный хост для WEB сервисов, который реализовал в себе весь необходимый функционал, но, увы, он сделан под конкретную задачу, а не как универсальное приложение.


Такой подход логирования и разбивания на отдельные модули, позволяет с лёгкостью выявить узкие моменты при запуске в новом окружении. Для примера, при разворачивании массива модулей на Windows 10, обнаружил, что загрузка, занимает времени намного больше, чем на других версиях ОС. Даже на моей старенькой машине с WinXP, загрузка 35 модулей выполняется максимум за 5 сек. Но на Win10 процесс загрузки одного единственного модуля занимал куда больше времени.



Благодаря независимой архитектуре, локализовать проблемный модуль удалось мгновенно. (В данном случае проблема была в использовании рантайма v2.0 под Windows 10).


Готовые модули


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


Web Service/Windows Communication Foundation Test Client



В основе этого приложения лежит приложение, идущее совместно с Visual Studio — WCF test client. На мой взгляд, в первоисточнике масса неудобных моментов. К моменту перехода на WCF у меня уже было написано много приложений на обычных WebService'ах. Изучив принципы работы самой программы через ILSpy, я решил расширить функциональность не только WCF, но и WS клиентов. В итоге, разобрав основную программу, я написал плагин со следующим расширенным функционалом:


  1. Поддержка WebService приложений (кроме Soap Header),
  2. Возможность тестирования сервиса со старыми binding'ами (при открытии не обновляет прокси-класс автоматом, а только по запросу из UI),
  3. Независимость от Visual Studio (объединил зависимые сборки через ILMerge),
  4. Вид всех добавленных сервисов в виде дерева, а не работа только с одним сервисом,
  5. Функция поиска по всем узлам дерева,
  6. На форму запроса сервиса добавлен таймер, чтобы отслеживать затраченное время на полное выполнение запроса,
  7. Добавлено восстановление отправленных параметров при закрытии и открытии формы теста или всего приложения,
  8. Добавлена возможность сохранения и загрузки параметров в файл по кнопке на форме теста метода.
  9. Добавлена возможность автосохранения и загрузки параметров метода (Понадобится модуль Plugin.Configuration > Auto save input values [False])
  10. Сломана возможность редактирования .config файла через программу SvcConfigEditor.exe


RDP Client



Опять же, первоисточником программы стали программисты из M$. В основе программы лежит программа RDCMan, но, в отличие от основной программы, я решил встроить окно подключённого сервера в диалоговый интерфейс. А удалённое хранилище настроек, помогло держать список серверов у всех причастных коллег в актуальном состоянии.



PE Info



В первоисточнике этого приложения лежит новая идея по автоматизации, которую я не смог найти в других приложениях. Цели написания такого приложения было 3:


  1. Предоставить интерфейс для просмотра содержимого PE файла, включая большинство директорий и таблиц метаданных (Хотя вывод ресурсов RT_DIALOG существенно отличается от оригинала).
  2. Поиск по структуре PE/CLI файлов
  3. Дать возможность загрузки PE файла не только из файловой системы, но и через WinAPI функцию LoadLibrary. В случае загрузки через LoadLibrary, есть шанс прочитать распакованный PE файл и не надо высчитывать RVA.

Несколько раз получалось, что исполняемые файлы реализовывали некий функционал, но этот функционал либо устаревал, либо никем не использовался. Чтобы не искать по исходным кодам приложений на разных языках использование тех или иных объектов и написано это приложение. Для примера, у меня есть сборка в общем репозитории и я решил удалить из этой сборки один метод. Как узнать, используется ли этот метод в текущих зависимых сборка других проектов написанными коллегами? Можно попросить проверить всех исходный код, можно посмотреть поискать в Source Control, а можно просто поискать одноимённый метод внутри скомпилированных сборок. Оно состоит из 2х компонентов:


  1. Сборка PEReader (написана без unsafe маркера), исходники которой доступны на GitHub'е,
  2. Клиентской части, которая представляет собой плагин для SAL инфраструктуры, используя уровень абстракции SAL.Windows.

Для поиска по иерархии PE, DEX, ELF и ByteCode файлов, был написан отдельный модуль, который замечательно вписался в инфраструктуру: ReflectionSearch. В данный модуль была вынесена вся логика поиска по объектам через рефлексию и благодаря нескольким публичным методам в модулях чтения исполняемых программ, удалось добиться многоразовости кода.


Остальные


Чтобы не описывать каждым отдельным пунктом весь список готовых модулей, я опишу оставшиеся модули одним списком:


  1. ELF Image Info — Разборка ELF файла по аналогии с PE Info. ElfReader на GitHub.
  2. ByteCode (.class) Info Разборка JVM .class файла. ByteCode Reader на GitHub
  3. DEX (Davlik) Info — Разборка DEX формата, который используется в Андройд приложениях. DexReader на GitHub
  4. Reflection Search — Сборка для поиска по объектам через рефлексию. Раньше была в составе модуля PE Info, но с появлением других модулей, была перенесена в отдельный модуль, используя публичные методы PE, ELF, DEX и ByteCode модулей.
  5. .NET Compiler — Компилятор .NET кода в реальном времени в текущем AppDomain. Предоставляет возможность написания кода (TextBox), хостинга скомпилированного приложения, кеширования скомпилированного кода и хранения скомпилированного кода как в виде отдельной сборки (Используется во второй итерации автоматизации приложения HTTP Harvester [Описан ниже]).
  6. Browser — Хостинг для Trident'а с расширенным функционалом получения XPath (самописный, на подобии HtmlAgilityPack) к DOM элементам. (Используется на третьей итерации автоматизации приложения HTTP Harvester [Описан ниже]).
  7. Configuration — Пользовательский интерфейс для редактирования настроек плагинов, ибо не все настройки доступны через UI при использовании SAL.Windows.
  8. Members — Отображение в UI public элементов плагинов, которые доступны для вызова извне.
  9. DeviceInfo — Сборка, способная прочитать S.M.A.R.T. атрибуты с совместимых устройств и работает без unsafe маркера. Для получения всех данных используется WinAPI функция DeviceIOControl, исходный код самой сборки доступен на GitHub'е.
  10. Single Instance — Ограничение приложения единственным экземпляром (Обмен ключами осуществляется через .NET Remoting),
  11. SQL Settings Provider — Провайдер сохранения и загрузки настроек из MSSQL. (код писался на ADO.NET и хранимых процедурах с размахом на унификацию, поэтому для отдельных СУБД придётся писать свои реализации хранимок),
  12. SQL Assembly scripter — Создание Microsoft SQL Server скрипта из .NET сборки для установки управляемого кода в MSSQL (не проверен на unsafe сборках),
  13. Winlogon — Модуль предоставляет публичные события для SENS интерфейсов. Первая версия использовала Winlogon, но он больше не поддерживается.
  14. EnvDTE.PublishCmd — Этот модуль я детально описал тут.
  15. EnvDTE.PublishSql — Перед или после ручной публикации выполненяет произвольный SQL запрос через ADO.NET с указанием шаблонных значений.

Остальные тут (Всего выложено около 30 модулей). Изображения всех модулей тут.


Готовые решения


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


  • Полная независимость модулей между собой
  • Частичная зависимость от Kernel модуля

TTManager



Приложение для системы задач, которое в основе использовало систему динамического расширения с возможностью использования разных источников задач. В итоге получился унифицированный интерфейс, который способен создавать, экспортировать/импортировать, просматривать задачи из разных источников. На текущий момент поддерживает в качестве источника MSSQL, WebService и частично REST API задач Мегаплана (не реклама). WebService написан по аналогичному принципу, с использованием базовых классов SAL.Web. Так что сам WebService также могут использовать в качестве источника MSSQL, Мегаплан или опять WebService.


Как работает

Kernel плагин приложения, ленивой загрузкой ищет все плагины источников задач (DAL). Если найдено несколько плагинов доступа к данным, то клиенту предлагается выбрать тот плагин, который он хочет использовать (Только в SAL.Windows, в хостах без пользовательского интерфейса — вылетит с ошибкой). Зависимые плагины получают доступ к выбранному DAL плагину через Kernel модуль.


Интересные моменты

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

Для решения проблемы со статусами задач, внутри некоторых DAL плагинов зашита матрица статусов (Или берутся из источника задач, если есть). В таком случае не возникает проблем с переносом данных из одного источника в другой.


HTTP Harvester




Приложение позволяет, используя готовые плагины, парсить сайты через Trident или WebRequest. Для парсинга доступно несколько уровней абстракции. Самый низкий уровень позволяет написать дополнительный плагин, который будет заниматься открытием и парсингом ответа, используя DOM или ответ от сервера. Уровень выше предлагает написать .NET код в рантайме, который через плагин “.NET Compiler” будет скомпилирован и применён к результату страницы, отображаемой в Trident'е в рантайме. Самый высокий уровень предполагает указание, через UI, элементов на странице сайта отображаемой в Trident’e. И после применения xpath (самописный вариант) шаблона, передать на обработку в универсальный плагин или выполнить .NET код из плагина ".NET Compiler".


Как работает

Модулю, зависимому от Kernel плагина, предлагается выбрать один из готовых интерфейсов вывода и базовый пользовательский интерфейс скачивания данных. Либо Trident, либо WebRequest с возможностью логирования. Kernel предлагает не только интерфейс, но и таймер опрашивания каждого отдельного модуля.


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


Интересные моменты

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


Приложение писалось в 3 итерации (Только под SAL.Windows):


  1. Сделана возможность написать плагин используя базовые элементы управления и массив методов работы с Trident описанные в Kernel плагине
  2. Появилась возможность заменять код в плагина используя рантайм код генерируемый и редактируемый в Plugin.Compiler
  3. Появилась возможность указывать в Trient путь к узлам HTML через UI. В результате для рантайм или онлайн кода отдаётся массив Ключ/Значение, где значением является путь к HTML элементу(ам) на подобии реализации в HtmlAgilityPack)


Что уже устарело и удалено


  1. Удалён Host для Office 2010. Он был написан исключительно для возможности создавать из контекстного меню задачу для TTManager, но из-за обилия костылей и ограниченности возможностей, дальнейшая поддержка оказалась нецелесообразной.
  2. Удалена возможность создания окон в EnvDTE через ATL. До VS 2007 возможность создания окон в студии была реализована только через ATL и COM. Затем появилась возможность всё делать через .NET.
  3. Устарел хост для EnvDTE реализованный как Add-In


Известные ошибки


Хост EnvDTE проверен только на английских студиях. Могут возникнуть проблемы на локализованных версиях (Один раз испытал на VS11 с русской локализацией).


Хост EnvDTE закрывает студию, если подгружен плагин Winlogon (SENS) и пользователь решил выгрузить хост через Add-in Manager. (Встретил на Windows 10).


Т.к. Хост написан как Add-In, а не как полноценное расширение, то совместимости с другими продуктами на основе EnvDTE — нет.


Какие прогнозы дальнейшего развития


При желании использовать функции кеширования, в довесок к встроенным классам System.Web.Caching.Cache и System.Runtime.Caching.MemoryCache, доступны удалённые кеши. Для примера, AppFabric. Написав базовый интерфейс клиента для кеширования, можно разработать массив модулей для каждого вида кеша и выбирать нужный модуль по необходимости (На момент публикации уже написаны, но не выложены).


Модули на момент написания могут подгружаться с файловой системы, с файловой системы в память и обновляться по сети, используя в качестве TOC XML файл. Дальнейшее развитие позволяет использовать в качестве хранилища не только с файловой системы, но и использовать nuget как хранилище или реализовать хост, который позволяет запускать модули удалённо.


Персонализация пользователя возможна как Roles, так и Claims. Но при использовании OpenId, OAuth, OpenId Connect, провайдеров существует огромное множество, при этом от каждого провайдера требуется получить System.Security.Principal.IIdentity (При использовании Roles based auth) или System.Security.Claims.ClaimsIdentity (При использовании Claims аутентификации). Соответственно, один раз написав клиента для LinedIn'а, можно его использовать в любом приложении без перекомпиляции.


При использовании очередей сообщений можно написать модуль и набор интерфейсов, который будет выполнять функции ServiceBus, а модули реализации конкретной очереди уже будут отвечать за получение и отправку сообщений.


Можно написать UI интерфейс динамического связывания публичных методов модулей, по аналогии с SSIS или BizTalk сервисами.

Поделиться с друзьями
-->

Комментарии (14)


  1. lair
    03.10.2016 13:29
    +2

    Несколько занятных вопросов.


    1. Какую именно логику вы повторно используете между разными хостами (особенно — между Windows Forms и asp.net)?
    2. Как вы реализуете эффективную работу с IO (все обращения к удаленным сервисам) без Task и async?
    3. Почему вы не взяли готовую систему плагинов (начиная с MEF)?


    1. ZetaTetra
      03.10.2016 15:03
      +1

      Какую именно логику вы повторно используете между разными хостами (особенно — между Windows Forms и asp.net)?

      DAL во всех случаях переносится на «ура». В теории можно переносить и BLL, если в логике нет взаимодействия напрямую с пользователем.
      Для примера, в приложении TTManager, который вскользь упомянут в статье, в ядре BLL, Windows.Forms используется в двух случаях:
      1) Для аутентификации, с аргументом Boolean silent (Т.е. либо вылетит с исключением, либо сначала покажет WinForms окно с вводом логина и пароля).
      2) Для выбора DAL плагина, если их найдено более одного. Соответственно, в ASP.NET окружении, при задвоении DAL плагинов, приложение вылетит с ConfigurationException.

      Сейчас в процессе хост на OWIN, который позволит отдавать методы помеченные как public, в виде REST методов. Для примера:
      //server.com/MyApplication/Plugins — Список всех подгруженных модулей и публичные методы в удалённом хосте
      //server.com/MyApplication/Plugin/{PluginId}/{MethodName}/ [POST: payload] — SET методы, GET: [queryString payload] — GET методы.
      Увы, пока меня ещё не всё устраивает в текущей версии OWIN хоста, поэтому и не выпустил…

      Как вы реализуете эффективную работу с IO (все обращения к удаленным сервисам) без Task и async?

      Я-бы себя не назвал экспертом в асинхронных приложениях, поэтому вопрос эффективности достаточно спорный. Но если используемая версия фреймворка не позволяет использовать async, то я обходился ThreadPool либо TPL.

      Почему вы не взяли готовую систему плагинов (начиная с MEF)?

      MEF и MAF — действительно классные фреймворки, но я хотел сделать базовый слой, который содержит минимум кода и, соответственно, требует минимального фреймворка, дабы была возможность запускать приложение на WinXP или WinMobile 6.5. Ну и поменять рантайм в [web/app].config'е с v2.0 на v4.0 куда проще чем в обратную сторону.


      1. lair
        03.10.2016 15:08
        -2

        Сейчас в процессе хост на OWIN, который позволит отдавать методы помеченные как public, в виде REST методов.

        То есть то, что логика REST-сервисов и UI-приложений различается, вас не смущает?


        Но если используемая версия фреймворка не позволяет использовать async, то я обходился ThreadPool либо TPL.

        Для TPL нужен .net 4+. У вас в ядре — 2.0.


        дабы была возможность запускать приложение на WinXP или WinMobile 6.5.

        На дворе, вроде бы, 2016 год. Вам точно это нужно?


        1. izzholtik
          03.10.2016 15:27

          > 2016
          Знаю сайт, который не мог включить HTTPS из-за того, что у удного из админов был winXP, не работающий с let's encrypt x)


        1. ZetaTetra
          03.10.2016 15:35
          +2

          То есть то, что логика REST-сервисов и UI-приложений различается, вас не смущает?

          Увы, грамотного решения, которое позволит отбразить UI одновременно в ASP.NET и WinForms приложении я не нашёл. Давным давно, видел одно решение, которое пыталось отрендерить WinForms приложение через Silverlight, но о дальнейшем развитии приложения я не слышал.

          Про REST методы я говорю в контексте использования клиентских фреймворков, таких как ExtJS.

          Для TPL нужен .net 4+. У вас в ядре — 2.0.

          Да, ядро скомпилировано на 2.0, но при добавлении в конфиг рантайма 4.0, мы можем использовать в коде компоненты фреймворком выше. Т.е. указав в конфиге рантайм 4.0, я могу использовать плагины, которые используют TPL, MemoryCache, ClaimsIdentity и пр…
          При использовании рантайма 2.0 этот модуль просто не запустится и в логе будет сообщение:
          BadImageFormatException:
          This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded. (Exception from HRESULT: 0x8013101B)


          На дворе, вроде бы, 2016 год. Вам точно это нужно?

          Поддержка POSReady 2009 (Практически WinXP но с некоторыми свистелками) заканчивается в 2019 года.
          Casio IT-600 на складах ещё используется. Я пару лет назад писал про это устройство один коммент. Тут на хабре:
          https://habrahabr.ru/post/235869/#comment_7953111


  1. spinmozg
    03.10.2016 20:45

    Идея уже была реализована в Обероне и Компонентном паскале.
    Но это вовсе не значит, что не стоило ее реализовывать еще раз


    1. Vjatcheslav3345
      03.10.2016 21:55
      +1

      Тогда до кучи стоило помянуть Zonnon — являющийся кстати .NET языком и, самое главное, задать куда более интересный вопрос — а что с тех пор изменилось — что в этой реализации было сделано лучше предшествующих? Что ещё нужно будет сделать, чтобы уйти ещё дальше в сторону совершенства?


      1. ZetaTetra
        04.10.2016 17:00
        +1

        Что в этой реализации было сделано лучше предшествующих?

        Я преследовал несколько целей:
        1. Не завязывать модуль на конкретный хост. Иначе это превратится в очередной BizTalk.
        2. Лёгкое изымание сборки из модульной архитектуры и встраивание в приложение через reference.
          Не устроила Вас работа сборки в плагинной архитектуре, добавили её в проект через reference и продолжаете использовать публичные классы и интерфейсы.
          Либо наоборот, добавили класс наследуемый от IPlugin и встроили в модульную инфраструктуру


        Что ещё нужно будет сделать, чтобы уйти ещё дальше в сторону совершенства?

        Вот прямо сейчас, мне очень не хватает ClaimsIdentity провайдеров для распространённых у нас социалок. По работе сейчас внедряю отдельный SSO сервер на основе OpenId Connect, и приходится вручную переносить весь «завод» провайдеров из старой OAuth аутентификации в IAppBuilder… Эта ручная работа немного угнетает…

        И было-бы неплохо реализовать ServiceBus, который мог-бы работать как с RabbitMQ, так и с MSMQ. Сейчас в ентерпрайзе крутится код с MSMQ, но он ещё недостаточно поработал, чтобы его можно было-бы оформить отдельным модулем.

        По поводу OWIN и REST диалога между одним хостом и другим, то это чисто познавательная работа, ибо есть идея, сделать из одного приложения толстого клиента. Т.е. либо оно работает полностью локально через локальный провайдер плагинов, либо оно становится толстым клиентом и грузит данные из облака через REST провайдер плагинов. А в облаках крутится хост, который грузит локально те плагины, которые раньше были локально.
        Что из этого выйдет — пока сам не знаю…


        1. Vjatcheslav3345
          05.10.2016 11:21
          +1

          Можно вложить в архитектуру следующий принцип — для принятия обоснованного решения разработчик, должен прочитать как можно меньше кода (тогда ваши наработки будут ценны в бизнесе, где, как известно время-деньги (которые д. б. ещё вчера)), модульная архитектура очень этому поспособствует (см. https://habrahabr.ru/post/311014/#first_unread
          а про идеи, которые стоит воплотить в виде состояния "из_коробки" в тестовой оснастке для архитектуры стоит посмотреть:
          тут описаны проблемы
          https://habrahabr.ru/company/jugru/blog/309502/#first_unread
          здесь-показана автоматизация тестирования как пример решения (неидеального)
          https://habrahabr.ru/company/acronis/blog/282682/#first_unread) — про бизнес-ценность этого — написал выше.


          1. ZetaTetra
            05.10.2016 22:42

            Полностью с Вами согласен, с абстракциями надо быть аккуратней. Я тут как раз ниже ответил про один из проектов, который нам достался от аутсорсеров:
            https://habrahabr.ru/post/303032/?reply_to=9842418#comment_9841066

            А вот про это:

            https://habrahabr.ru/post/311014/#first_unread

            За всё то время, сколько я проработал в проектах на .NET'е, я обратил внимание на 2 проблемы, которые способствуют замусориванию кода:
            1) Нежелание чистить код или пересматривать весь кусок, если того требует итерация.
            2) Оставить или написать заранее код по принципу «А вдруг пригодится».

            Причём как с первой проблемой я сталкивался не очень часто. А вот со второй проблемой, увы, наталкиваюсь чуть-ли не постоянно… Причём такие косяки встречаются даже у авторов учебников по программированию… (Первый раз натолкнулся в книге Дино Эспозито про первую версию MVC).

            Так что я думаю, что многие вредные абстракции как раз выливаются из выражения «А вдруг пригодится».


            1. Vjatcheslav3345
              11.10.2016 09:54

              1) Нежелание чистить код или пересматривать весь кусок, если того требует итерация.
              2) Оставить или написать заранее код по принципу «А вдруг пригодится».

              1) Нежелание чистить код или пересматривать весь кусок, если того требует итерация.
              2) Оставить или написать заранее код по принципу «А вдруг пригодится».

              Страх человека:
              А) перед лишними усилиями;
              Б) перед ошибками (удалил действительно пригодившуюся впоследствии функциональность),
              заставляет так его вести себя. Избавьте его от страхов с помощью модульности, системы версий и жить станет проще — сейчас, например пункт Б уже не столь актуален, если есть версии.


              Кстати — большинство современных языков появились из исследовательских прототипов, решающих частные задачи конкретного исследователя — просто программисты гиенами накинулись на новинку и потащили её в промразработку, не думая о последствиях. Только некоторые из языков проектировались с явным и чётким намерением использовать их в промразработке: скажем, — Сobol, Ada, Java, C#, в телекоме — Erlang. Например, если о безопасности языка начали помаленьку думать на стадии его проектирования то, мало кого из академических разработчиков языков интересует проблема производительности обычного офисного программиста.
              Ещё раз кстати, раз архитектура применяется на таком месте как склады, то можно изначально добавить в неё новую, важную для бизнеса возможность — возможность крутиться на сколь угодно старой технике и без проблем переходить на новую — бизнесу важно отбить затраты, поэтому можно ожидать, что в следующие 50-60 лет учёт на складах (и не только — везде почти так будет — так в своё время было с радиотехникой и телефоном: сначала это были военные разработки или высокие технологии — а потом радиоаппаратура и телефоны в каждой квартире и на каждом рабочем месте) будут вести на технике, пока она не сдохнет с коротким замыканием (https://habrahabr.ru/company/ibm/blog/177683/) — с приходом "интернета вещей" бизнес породит спрос на дешевые, долговечные, нетребовательные промкомпьютеры. Но такой подход потребует специализированного, ориентированного на долговечность и "железонезависимость" проектирования встроенных контейнеров и виртуальных машин, "абстрактных машин языка программирования", мощной ориентированности на облака и сети, ориентированности на производительность программистов.
              Всё это тоже можно попробовать прикрутить к Вашему комбайну.


    1. ZetaTetra
      04.10.2016 12:02

      Тогда ещё стоит упомянуть про OSGi на Java:
      https://ru.wikipedia.org/wiki/OSGi


  1. ZOXEXIVO
    03.10.2016 22:34
    +1

    Иногда смотришь и не можешь понять, зачем люди делают некоторые вещи, а потом понимаешь — просто переклинило человека и он сидит на своей волне и воообще не понимает потребностей текущих реалий.
    Никому не нужны такие комбайны — в том числе и вам. Это неэффективно, негибко и очень сложно в поддержке, особенно, если какой-то функционал захочет большего и в угоду ему придется перетягивать одеяло.
    Тем более сидеть на .NET 2.0 это вообще не комментируемо даже.
    Ладно бы интересно было разрабатывать, но писать все на инструменте 10-летней давности — это за гранью.
    Может проще на ваших складах поменять аппаратную часть? Или хотите положить вашу жизнь на разработку системы, от которой можно отказаться щелчком пальца и тупо поменяв аппаратную часть и софт?


    1. ZetaTetra
      04.10.2016 13:47
      +1

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

      Я ждал этого комментария :)

      Никому не нужны такие комбайны — в том числе и вам

      От чего-же комбайны? Наоборот, код получается очень лёгким. При этом, если готовое решение Вам не подходит, Вы можете написать своё и не использовать готовые. Смысл в том, что хост может быть универсальным, таким как MDI,SDI и пр… А может быть узкоспециализированным и заточенным под конкретну задачу. Для примера: http://www.zetuniverse.com/
      При этом, использовать уже написанные модули.

      Если взять с архитектурной области, то вот Вам несколько практик из жизни:
      1) RabbitMQ на ферме с большим количеством виндовых сервисов
      2) Масштабирование приложения на ферму и перенос кеша с MemoryCache на кеширующие сервера
      3) Слоёное накладывание тестов, начиная от отдельного компонента, заканчивая решением целиком

      Это неэффективно, негибко и очень сложно в поддержке

      А почему сложно поддерживать?

      особенно если какой-то функционал захочет большего и в угоду ему придется перетягивать одеяло

      Это зависит от того, насколько правильно Вы выстроили архитектуру. В противном случае, если архитектура в приложении выстроена неправильно, то проще всё приложение выкинуть на помойку…

      Нам сейчас досталось одно MVC приложение на поддержку от аутсорсеров, где PM подразумевал итеративное развитие проекта, а разработчики решили выстроить водопадое развитие… Да, с одной стороны всё написано грамотно, разделено на слои (а затем ещё на слои, и ещё на слои...) с использованием Entity FW в качестве ORM.
      Но как только от бизнеса пришло условие переписать всё на базу с использованием фиксированных методов, то 70% кода пришлось просто выбросить и оставить только вьюхи. Хотя я не сторонник переписывать всё что пришло от других комманд, уже наступал на эти грабли…

      Тем более сидеть на .NET 2.0 это вообще не комментируемо даже

      CLR v2.0 — это минимум для интерфейсов.
      У Вас есть преложения, как улучшить интерфейсы с использованием CLR > v2.0?

      Влияет-ли это на производительность? Давйте проверим:
      Напишем простое WinForms приложение на, скажем, .NET 3.5 и запустим его под CLR v2.0. При запросе:
      System.AppDomain.CurrentDomain.GetAssemblies() увидим следующую картину:
      mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      SampleApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
      System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
      System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a


      Затем, откроем SampleApp.config и добавим следующий код сразу после </configSections>:
      	<startup>
      		<supportedRuntime version="v4.0" />
      		<supportedRuntime version="v2.0.50727" />
      	</startup>
      


      Опять запускаем наше приложение:
      mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      SampleApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
      System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
      System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
      System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a


      Т.е. благодаря AssemblyRedirect, мы запустились в окружении CLR 4.0.
      Соответственно, дальнейшее использование компонентов ограничено только возможностями последнего .NET Framework'а.

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

      Увы, аппаратная часть на всех складах на территории бывшего СНГ «щелчком пальца» не меняется. Поэтому при переходе на более новую аппаратную часть, отпадает только часть визуальных компонентов, которые заточены под конкретное устройство.