Your browser may have trouble rendering this page. See supported browsers for more information.

This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.

Title

Encodo's configuration library for Quino: part III

Description

<img attachment="applicationconfigurationlogo.png" align="right">This discussion about configuration spans three articles: <ol> <b><a href="{app}view_article.php?id=3132">part I</a></b> discusses the history of the configuration system in Quino as well as a handful of principles we kept in mind while designing the new system <b><a href="{app}view_article.php?id=3138">part II</a></b> discusses the basic architectural changes and compares an example from the old configuration system to the new. <b>part III</b> takes a look at configuring the "execution order"---the actions to execute during application startup and shutdown </ol> <h>Introduction</h> Registering with an IOC is all well and good, but <i>something</i> has to make calls <i>into</i> the IOC to get the ball rolling. <pullquote align="right" width="250px">Something has to actually make calls into the IOC to get the ball rolling.</pullquote>Even service applications---which start up quickly and wait for requests to do most of their work---have basic operations to execute before declaring themselves ready. Things can get complex when starting up registered components and performing basic checks and non-IOC configuration. <ul> In which order are the components and configuration elements executed? How do you indicate dependencies? How can an application replace a piece of the standard startup? What kind of startup components are there? </ul> Part of the complexity of configuration and startup is that developers quickly forget all of the things that they've come to expect from a mature product and start from zero again with each application. Encodo and Quino applications take advantage of prior work to include standard behavior for a lot of common situations. <h>Configuration Patterns</h> Some components can be configured once and directly by calling a method like <c>UseMetaTranslations(string filePath)</c>, which includes all of the configuration options directly in the composition call. This pattern is perfect for options that are used only by one action or that it wouldn't make sense to override in a subsequent action. So, for simple actions, an application can just replace the existing action with its own, custom action. In the example above, an application for which translations had already been configured would just call <c>UseMetaTranslations()</c> again in order to override that behavior with its own. <pullquote align="left" width="250px">Most application will replace standard actions or customize standard settings</pullquote>Some components, however, will want to expose settings that can be customized by actions before they are used to initialize the component. For example, there is an action called <c>SetUpLoggingAction</c>, which configures logging for the application. This action uses <c>IFileLogSettings</c> and <c>IEventLogSettings</c> objects from the IOC during execution to determine which types of logging to configure. An application is, of course, free to replace the entire <c>SetUpLoggingAction</c> action with its own, completely custom behavior. However, an application that just wanted to change the log-file behavior or turn on event-logging could use the <c>Configure<tservice>()</c> method<fn>, as shown below. <code> application.Configure<ifilelogsettings>( s => s.Behavior = LogFileBehavior.MultipleFiles ); application.Configure<ieventlogsettings>( s => s.Enabled = true ); </code> <h>Actions</h> A Quino application object has a list of <c>StartupActions</c> and a list of <c>ShutdownActions</c>. Most standard middleware methods register objects with the IOC and add one or more actions to configure those objects during application startup. Actions have existed for quite a while in Quino. In Quino 2, they have been considerably simplified and streamlined to the point where all but a handful are little more than a functional interface<fn>. The list below will give you an idea of the kind of configuration actions we're talking about. <ul> Load configuration data Process command line Set up logging Upgrade settings/configuration (e.g. silent upgrade) Log a header (e.g. user/date/file locations/etc.; for console apps. this might be mirrored to the console) Load plugins Set up standard locations (e.g. file-system locations) </ul> For installed/desktop/mobile applications, there's also: <ul> Initialize UI components Provide loading feedback Check/manage multiple running instances Check software update Login/authentication </ul> Quino applications also have actions to configure metadata: <ul> Configure expression engine Load metadata Load metadata-overlays Validate metadata Check data-provider connections Check/migrate schema Generate default data </ul> Application shutdown has a smaller set of vital cleanup chores that: <ul> dispose of connection managers and other open resources write out to the log, flush it and close it show final feedback to the user </ul> <h>Anatomy of an Action</h> The following example<fn> is for the 1.x version of the relatively simple <c>ConfigureDisplayLanguageAction</c>. <code> public class ConfigureDisplayLanguageAction<tapplication> : ApplicationActionBase<tapplication> where TApplication : ICoreApplication { public ConfigureDisplayLanguageAction() : base(CoreActionNames.ConfigureDisplayLanguage) { } protected override int DoExecute( TApplication application, ConfigurationOptions options, int currentResult) { // Configuration code... } } </code> What is wrong with this startup action? The following list illustrates the main points, each of which is addressed in more detail in its own section further below. <ul> The <c>ConfigurationOptions</c> parameter introduces an unnecessary layer of complexity The generic parameter <c>TApplication</c> complicates declaration, instantiation and extension methods that use the action The <c>int</c> return type along with the <c>currentResult</c> parameter are a bad way of controlling flow. </ul> The same startup action in Quino 2.x has the following changes from the Quino 1.x version above (legend: <hl>additions</hl>; <del>deletions</del>). <code> public class ConfigureDisplayLanguageAction<del><tapplication></del> : ApplicationActionBase<del><tapplication> where TApplication : ICoreApplication</del> { public ConfigureDisplayLanguageAction() : base(CoreActionNames.ConfigureDisplayLanguage) { } <hl>public</hl><del>protected</del> override <hl>void </hl><del>int Do</del>Execute( <del>TApplication application, ConfigurationOptions options, int currentResult</del>) { // Configuration code... } } </code> As you can see, quite a bit of code and declaration text was removed, all without sacrificing any functionality. The final form is quite simple, inheriting from a simple base class that manages the name of the action and overrides a single parameter-less method. It is now much easier to see what an action does and the barrier to entry for customization is much lower. <code> public class ConfigureDisplayLanguageAction : ApplicationActionBase { public ConfigureDisplayLanguageAction() : base(CoreActionNames.ConfigureDisplayLanguage) { } <hl>public override void</hl> Execute() { // Configuration code... } } </code> In the following sections, we'll take a look at each of the problems indicated above in more detail. <h>Remove the <c>ConfigurationOptions</c> parameter</h> These options are a simple enumeration with values like <c>Client</c>, <c>Testing</c>, <c>Service</c> and so on. They were used only by a handful of standard actions. These options made it more difficult to decide how to implement the action for a given task. If two tasks were completely different, then a developer would know to create two separate actions. However, if two tasks were similar, but could be executed differently depending on application type (e.g. testing vs. client), then the developer could still have used two separate actions, but could also have used the configuration options. Multiple ways of doing the exact same thing is all kinds of bad. <pullquote align="right" width="200px">Multiple ways of doing the exact same thing is all kinds of bad.</pullquote>Parameters like this conflict conceptually with the idea of using <i>composition</i> to build an application. To keep things simple, Quino applications should be configured <i>exclusively</i> by composition. Composing an application with service registrations and startup actions and <i>then</i> passing options to the startup introduced an unneeded level of complexity. Instead, an application now defines a separate action for each set of options. For example, most applications will need to set up the display language to use---be it for a GUI, a command-line or just to log messages in the correct language. For that, the application can add a <c>ConfigureDisplayLanguageAction</c> to the startup actions or call the standard method <c>UseCore()</c>. Desktop or single-user applications can use the <c>ConfigureGlobalDisplayLanguageAction</c> or call <c>UseGlobalCore()</c> to make sure that global language resources are also configured. <h>Remove the <c>TApplication</c> generic parameter</h> The generic parameter to this interface complicates the <c>IApplication<tapplication></c> interface and causes no end of trouble in <c>MetaApplication</c>, which actually inherits from <c>IApplication<imetaapplication></c> for historical reasons. <pullquote width="150px" align="left">There is no need to maintain statelessness for a single-use object.</pullquote>Originally, this parameter guaranteed that an action could be stateless. However, each action object is attached to exactly one application (in the <c>IApplication<tapplication>.StartupActions</c> list. So the action that is attached to an application is technically stateless, and a completely different application than the one to which the action is attached could be passed to the <c>IApplcationAction.Execute</c>...which makes no sense whatsoever. Luckily, this never happens, and only the application to which the action is attached is passed to that method. If that's the case, though, why not just create the action with the application as a constructor parameter when the action is added to the <c>StartupActions</c> list? There is no need to maintain statelessness for a single-use object. This way, there is no generic parameter for the <c>IApplication</c> interface, all of the extension methods are <i>much</i> simpler and applications are free to create custom actions that work with descendants of <c>IApplication</c> simply by requiring that type in the constructor parameter. <h>Debugging is important</h> <pullquote width="120px" align="left">A global exception handler is terrible for debugging</pullquote>The original startup avoided exceptions, preferring an integer return result instead. In release mode, a global exception handler is active and is there to help the application exit more or less smoothly---e.g. by logging the error, closing resources where possible, and so on. A global exception handler is terrible for debugging, though. For exceptions that are caught, the default behavior of the debugger is to stop where the exception is caught rather than where it is thrown. Instead, you want exceptions raised by your application to to stop the debugger from where they are <i>thrown</i>. So that's part of the reason why the startup and shutdown in 1.x used return codes rather than exceptions. <h>Multiple valid code paths</h> The other reason Quino used result codes is that most non-trivial applications actually have multiple paths through which they could successfully run. Exactly which path the application should take depends on startup conditions, parameters and so on. Some common examples are: <ul> Show command-line help Migrate an application schema Import, export or generate data </ul> To show command-line help, an application execute its startup actions in order. It reaches the action that checks whether the user requested command-line help. This action processes the request, displays that help and then wants to smoothly exit the application. The "main" path---perhaps showing the user a desktop application---should no longer be executed. <pullquote width="160px" align="right">Non-trivial applications have multiple valid run profiles.</pullquote>Similarly, the action that checks the database schema determines that the schema in the data provider doesn't match the model. In this case, it would like to offer the user (usually a developer) the option to update the schema. Once the schema is updated, though, startup should be restarted from the beginning, trying again to run the main path. <clear><h>Use exceptions to indicate errors</h> Whereas the Quino 1.x startup addressed the design requirements above with return codes, this imposes an undue burden on implementors. There was also confusion as to when it was OK to actually throw an exception rather than returning a special code. Instead, the Quino 2.x startup always uses exceptions to indicate errors. There are a few special types of exceptions recognized by the startup code that can indicate whether the application should silently---and successfully---exit or whether the startup should be attempted again. <h>Conclusion</h> There is of course more detail into which we could go on much of what we discussed in these three articles, but that should suffice for an overview of the Quino configuration library. <hr> <ft>If C# had them, that it is. See <a href="{app}view_article.php?id=365">Java 8</a> for an explanation of what they are.</ft> <ft>This pattern is echoed in the latest beta of the ASP.NET libraries, as described in the article <a href="http://www.strathweb.com/2015/03/strongly-typed-routing-asp-net-mvc-6-iapplicationmodelconvention/">Strongly typed routing for ASP.NET MVC 6 with IApplicationModelConvention</a>.</ft> <ft>Please note that formatting for the code examples has been adjusted to reduce horizontal space. The formatting does not conform to the <a href="{www-en}documents.php#csharp-handbook">Encodo C# Handbook</a>.</ft>