A test application for testing serializers was made on
the NFX library . This is Unistack library. Frankly, I find it difficult to name another example of the Unistack library. Maybe something similar is in the form of ServiceStack. Although ServiceStack, unlike NFX, is spread over several dll. But most importantly, ServiceStack is not Uni, since its parts are made a little differently, and it does not cover such a global space as NFX. But the purpose of this article is not to discuss the concept of Unistack, but one of the features of using NFX.
How did using NFX affect our test application? Let's get a look.
The test application is a console application. We run it and at the end we get the test results. It can be a lot of tests and it will be silly to drive all the tests in all combinations
in one pass . What would I do without NFX? Most likely I would add a few parameters to the command line in order to run only the tests I need
now . Having worked a little bit, I would add the configuration parameters to the Xml config file and read them from there. I would most likely use a simple array of parameters, the name - the value, in the appSettings section of the configuration file. You can build a more complex configuration structure, but supporting this in .NET is not so simple, and I would forget about the tests themselves for a while, but would develop and debug this configuration file. No, I would not do a complex structure, because - it is difficult, and the benefits obtained with its help are not so great.
In NFX, making a complex configuration is easy. It is so simple that it drastically changes the design of our test application.
')
Suppose I do not know anything about NFX and try to understand how it works, using the example of
our application .
I will open the
objgraph configuration file
. laconf . Here is a section that describes the tests themselves:
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 ...
Obviously, the tests section contains inside a collection of test sections, each of which defines the parameters of a single test. The first parameter is type, again it is obvious that it indicates the type (class) in assembly. In the first test, this is respectively the Serbench.Specimens.Tests.ObjectGraph class in the Serbench.Specimens assembly. All other parameters are also clear without further explanation.
Here is a section describing serializers:
serializers {
Nothing new, everything is clear, except that the _include construction appeared, pointing to the file.
All this is much like JSON. So far, the biggest difference is in using '=' instead of ':'. More collections are not allocated in a special way, in JSON - this is '[]', here it is the same '{}'.
Ok, now I'll go to the code itself and see which API is used to get to these configuration parameters.
Here is the
Test class that was specified in the config file:
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;
In the configuration we have
runs=1 ser-iterations=100 deser-iterations=100
and in the code - slightly changed parameters. For example, from m_SerIterations it turned out ser-iterations. That is, the variables in the configuration are all written in small letters. If the capital letter is found, it becomes small, but it is preceded by a '-'. And the prefix 'm_' is simply discarded.
Stop, but how can we understand that the variable from the code becomes configurable? Obviously, using the [Config] attribute.
Well, it is clear how the configuration is set. And how is it used? I'll try to deal with the section serializers.
I find it in the TestingSystem class:
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) ...
Now I see the work with the container. A separate Serializer class is registered for each serializer of the configuration.
What is m_Serializers?
private OrderedRegistry<Serializer> m_Serializers = new OrderedRegistry<Serializer>();
Similar code is for m_Tests, the same registration, but already Test classes.
doTestRun () method - to run one test run (runs = 1). It first starts the required number of serialization iterations (ser-iterations = 100), then the necessary number of deserialization iterations (deser-iterations = 100). All these parameters are set in the configuration.
Well, with the details like everything is clear. I'll be back.
Summary
If you now take another look at the application, you will see that this is no longer a typical console application with a couple of lines of configuration. Now the configuration has grown and become commensurate in size directly with the C # code. The configuration has become similar to the interfaces to the application. We do not have a UI, this is still a console application, but how much
business logic has moved from code to configuration !
How interesting was the configuration. There are settings for the entire application, and settings for individual classes. Now classes are more like business objects.
And - yes, you are right. Now the entire application can be developed, starting with the configuration definition, which is now our business interface. After that, you can proceed directly to the design of classes and coding.
Let's look again at what we added and what we received in return.
We began to use a configuration system from NFX. We first created a configuration file with business interfaces:
- Here are the tests we want to perform.
- Here are the serializers we want to test.
- Here is the test data.
- Here are the totals and their formats.
In other words, we first
described the model of our test application in the configuration .
The next step is to create concrete classes and link them to the configuration.
Questions from the programmer
Here we associate the configuration with specific classes not during the compilation of the application, but during its operation. How much will this increase the likelihood of run - time errors?Yes, now if we make a mistake, for example, in the name of the class in the configuration, then this error will not be detected during compilation, I only while the application is running. NFX loads the configuration when the application starts. Therefore, most of the errors are detected immediately at the start, and not during the operation of a separate class. In this case, the diagnosis uniquely localizes errors. As a result, the probability of run-time errors increases slightly.
The entire NFX is in one assembly . It has a lot of classes that I will not use. Do they interfere?The first thing you will notice when you first compile an application under NFX is how quickly the compilation will go. NFX is a library made by programmers for programmers. And it is intended for the most critical cases: thousands of servers, millions of messages, etc. Everything that slows down has been reworked or completely replaced. In addition, NFX is a library for servers, size is not so important for it. Although I do not think that 1.5 MB (NFX size) will be great for client applications.
Is it possible to use NFX in IoT devices? She has many opportunities, and all this is in one file.We honestly did not think about this. Still, NFX, do not forget, works on .NET. If there are devices with .NET loaded, then why not.
As I see, the configuration is set in some new language. Honestly, I don’t want to learn another language. Are there any alternatives?Yes there is. In the NFX configuration can be described still on Laconic or XML. At the same time, when switching between Laconic and XML, you don’t have to change absolutely anything in the code.
Why was Laconic language rather than JSON used for configurations? It is no more difficult than JSON and you can learn it in 5 minutes. Unfortunately, JSON, for a number of specific reasons, is poorly suited for configuration files.