Как стать автором
Обновить

Как конфигурация влияет на архитектуру приложения

Время на прочтение6 мин
Количество просмотров7.9K
Тестовое приложение для тестирования сериализаторов было сделано на библиотеке NFX. Это Unistack библиотека. Честно говоря, я затрудняюсь назвать другой пример Unistack библиотеки. Может быть что-то похожее есть в виде ServiceStack. Хотя ServiceStack, в отличии от NFX, размазан по нескольким dll. Но самое главное, ServiceStack не является Uni, так как его части сделаны немножко по-разному, и он не покрывает такого глобального пространства, как NFX. Но целью данной статьи не является обсуждение концепции Unistack, а одна из особенностей использования NFX.

Как использование NFX повлияло на наше тестовое приложение? Давайте посмотрим.

Тестовое приложение — это консольное приложение. Мы запускаем его и в конце получаем результаты тестов. Тестов может быть много и прогонять все тесты во всех комбинациях за один проход будет глупо. Что бы я сделал без NFX? Скорее всего добавил бы несколько параметров в командную строку, чтобы запускать только нужные мне сейчас тесты. Немножко поработав, я бы добавил конфигурационные параметры в Xml config файл и читал бы их оттуда. Я бы, скорее всего использовал простой массив параметров, имя — значение, в appSettings секции конфигурационного файла. Можно построить более сложную структуру конфигурации, но поддержка этого в .NET не так проста, и я бы на время забыл про сами тесты, а разрабатывал бы и отлаживал этот конфигурационный файл. Нет, я бы не стал делать сложную структуру, потому что — это сложно, а полученные с его помощью преимущества не так велики.

В NFX сделать сложную конфигурацию — просто. Это настолько просто, что это кардинально меняет дизайн нашего тестового приложения.

Предположим, я не знаю ничего о NFX и пытаюсь понять, как это работает, на примере нашего приложения.

Открою конфигурационный файл objgraph.laconf. Вот секция, описывающая сами тесты:

 tests
       {
           test
           {
            type="Serbench.Specimens.Tests.ObjectGraph, Serbench.Specimens"
            name="Warmup ObjectGraph"
            order=000
            runs=1
            ser-iterations=1
            deser-iterations=1
           }
           
           test
           {
            type="Serbench.Specimens.Tests.ObjectGraph, Serbench.Specimens"
            name="Conferences: 1; Participants: 250; Events: 10"
            order=000
            runs=1
            ser-iterations=100
            deser-iterations=100
           ...


Очевидно, секция tests содержит внутри коллекцию секций test, каждая из которых определяет параметры одного теста. Первый параметр — type, опять же очевидно, что он указывает на тип (класс) в assembly. В первом тесте — это соответственно, Serbench.Specimens.Tests.ObjectGraph класс в Serbench.Specimens assembly. Все остальные параметры тоже понятны без дополнительных разъяснений.

Вот секция, описывающая сериалайзеры:

serializers
   {
       // Stock serializers: they use only Microsoft .NET libraries          
       serializer
                    	{
                                type="Serbench.StockSerializers.MSBinaryFormatter, Serbench"
                                name="MS.BinaryFormatter"
                                order=10
                    	}        
       
                    	serializer
                    	{
                                type="Serbench.StockSerializers.MSDataContractJsonSerializer, Serbench"
                                name="MS.DataContractJsonSerializer"
                                order=20
                                _include { file="knowntypes.Conference.laconf"} //include file contents
                    	}
...

Ничего нового, все понятно, разве что появилась конструкция _include, указывающая на файл.

Все это сильно похоже на JSON. Пока что самое большое отличие от него в использовании ‘=’ вместо ‘:’. Еще коллекции не выделяются особым образом, в JSON — это ‘[]’, здесь это — те же ‘{}’.

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

Вот Testкласс, который был указан в config файле:

public abstract class Test : TestArtifact
…
   [Config(Default=100)]
   private int m_SerIterations;
 
   [Config(Default=100)]
   private int m_DeserIterations;
 
   [Config(Default=1)]
   private int m_Runs;


В конфигурации мы имеем

		runs=1
		ser-iterations=100
		deser-iterations=100


а в коде — немножко измененные параметры. К примеру, из m_SerIterations получилось ser-iterations. То есть переменные в конфигурации все пишутся маленькими буквами. Если встречается заглавная буква, то она становится маленькой, но перед ней ставится ‘-’. И префикс ‘m_’ просто отбрасывается.

Стоп, а как же мы поймем, что переменная из кода становится конфигурируемой? Очевидно, что с помощью атрибута [Config].

Хорошо, понятно, как задаётся конфигурация. А как она используется? Попробую разобраться с секцией serializers.

Нахожу ее в в классе TestingSystem:

       public const string CONFIG_SERIALIZERS_SECTION = "serializers";
       public const string CONFIG_SERIALIZER_SECTION = "serializer";
 
...
         foreach(var snode in node[CONFIG_SERIALIZERS_SECTION].Children.Where(cn => cn.IsSameName(CONFIG_SERIALIZER_SECTION)))
         {
             var item = FactoryUtils.Make<Serializer>(snode, args: new object[]{this, snode});
             m_Serializers.Register( item  );
             log(MessageType.Info, "conf sers", "Added serializer {0}.'{1}'[{2}]".Args(item.GetType().FullName, item.Name, item.Order));
         }
 
         if (m_Serializers.Count==0)
...


Теперь я вижу работу с контейнером. Регистрируется отдельный Serializer класс для каждого serializer из конфигурации.

А что такое m_Serializers?

       private OrderedRegistry<Serializer> m_Serializers = new OrderedRegistry<Serializer>();


Похожий код — для m_Tests, та же регистрация, но уже Test классов.

doTestRun() метод — для запуска одного тестового прохода (runs=1). Он запускает сначала нужное количество итераций сериализации (ser-iterations=100), потом нужное количество итераций десериализации (deser-iterations=100). Все эти параметры задаются в конфигурации.

Хорошо, с деталями вроде все понятно. Вернусь назад.

Резюме


Если теперь заново взглянуть на приложение, то увидим, что это уже не типичное консольное приложение с парой строчек конфигурации. Теперь конфигурация разрослась и стала по размеру соизмерима непосредственно с кодом на С#. Конфигурация стала похожа на интерфейсы к приложению. У нас нет UI, это по-прежнему консольное приложение, но насколько сильно бизнес логика переместилась из кода в конфигурацию!

Насколько интересной стала конфигурация. Здесь есть и настройки всего приложения, и настройки отдельных классов. Теперь классы больше похожи на бизнес-объекты.

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

Давайте еще раз посмотрим, что мы добавили и что получили взамен.

Мы стали использовать конфигурационную систему из NFX. Мы сначала создали конфигурационный файл с бизнес-интерфейсами:

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


Другими словами, мы сначала описали модель нашего тестового приложения в конфигурации.

Следующим шагом мы создали конкретные классы и связали их с конфигурацией.

Вопросы от программиста



Здесь мы связываем конфигурацию с конкретными классами не во время компиляции приложения, а во время его работы. Как сильно это увеличит вероятность run-time ошибок?

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


Весь NFX находится в одном assembly. В нем масса классов, которые я не буду использовать. Мешаются ли они?

Первое, что вы заметите, когда первый раз скомпилируете приложение под NFX, это — как быстро пройдет компиляция. NFX — библиотека, сделанная программистами для программистов. И предназначена она для самых критических случаев: тысячи серверов, миллионы сообщений и т.п. Все, что тормозит, было переработано или полностью заменено. Кроме того, NFX — это библиотека для серверов, размер для нее не так важен. Хотя не думаю, что 1.5 МБ (размер NFX) будет велика и для клиентских приложений.


А можно ли использовать NFX в IoT устройствах? Возможностей у нее много, и все это — в одном файле.

Мы над этим, честно говоря, не думали. Все же NFX, не забывайте, работает на .NET. Если будут устройства с загруженным .NET, то — почему бы и нет.


Как я вижу, конфигурация задается в каком-то новом языке. Честно говоря, не хочу изучать еще один язык. Есть какие-нибудь альтернативы?

Да, есть. В NFX конфигурации можно описывать еще на Laconic или на XML. При этом при переходе между Laconic и XML вам не придется менять абсолютно ничего в коде.

Почему для конфигураций был использован язык Laconic, а не JSON? Он не сложнее JSON и выучить его можно за 5 минут. К сожалению, JSON, по ряду конкретных причин, плохо подходит для конфигурационных файлов.
Теги:
Хабы:
Всего голосов 9: ↑6 и ↓3+3
Комментарии60

Публикации

Истории

Работа

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань