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


Quino 2: Starting up an application, in detail


As part of the final release process for Quino 2, we've upgraded 5 solutions<fn> from Quino 1.13 to the latest API in order to shake out any remaining API inconsistencies or even just inelegant or clumsy calls or constructs. A lot of questions came up during these conversions, so I wrote the following blog to provide detail on the exact workings and execution order of a Quino application. I've discussed the design of Quino's configuration before, most recently in <a href="{app}view_article.php?id=422">API Design: Running an Application (Part I)</a> and <a href="{app}view_article.php?id=426">API Design: To Generic or not Generic? (Part II)</a> as well as the three-part series that starts with <a href="{app}view_article.php?id=412">Encodo’s configuration library for Quino: part I</a>. <h>Quino Execution Stages</h> The life-cycle of a Quino 2.0 application breaks down into roughly the following stages: <ol> <b>Build Application</b>: Register services with the <abbr title="Inversion Of Control container">IOC</abbr>, add objects needed during configuration and add actions to the startup and shutdown lists <b>Load User Configuration</b>: Use non-IOC objects to bootstrap configuration from the command line and configuration files; IOC is initialized and can no longer be modified after action <c>ServicesInitialized</c> <b>Apply Application Configuration</b>: Apply code-based configuration to IOC objects; ends with the <c>ServicesConfigured</c> action <b>Execute</b>: execute the loop, event-handler, etc. <b>Shut Down</b>: dispose of the application, shutting down services in the IOC, setting the exit code, etc. </ol> <h>Stage 1</h> The first stage is all about putting the application together with calls to <c>Use</c> various services and features. This stage is covered in detail in three parts, starting with <a href="{app}view_article.php?id=412">Encodo’s configuration library for Quino: part I</a>. <h>Stage 2</h> Let's tackle this one last because it requires a bit more explanation. <h>Stage 3</h> Technically, an application can add code to this stage by adding an <c>IApplicationAction</c> before the <c>ServicesConfigured</c> action. Use the <c>Configure<tservice>()</c> extension method in stage 1 to configure individual services, as shown below. <code> application.Configure<ifilelogsettings>( s => s.Behavior = FileLogBehavior.MultipleFiles ); </code> <h>Stage 4</h> The execution stage is application-specific. This stage can be short or long, depending on what your application does. For desktop applications or single-user utilities, stage 4 is executed in application code, as shown below, in the <c>Run</c> method, which called by the <c>ApplicationManager</c> after the application has started. <code> var transcript = new ApplicationManager().Run(CreateApplication, Run); IApplication CreateApplication() { ... } void Run(IApplication application) { ... } </code> If your application is a service, like a daemon or a web server or whatever, then you'll want to execute stages 1--3 and then let the framework send requests to your application's running services. When the framework sends the termination signal, execute stage 5 by disposing of the application. Instead of calling <c>Run</c>, you'll call <c>CreateAndStartupUp</c>. <code> var application = new ApplicationManager().CreateAndStartUp(CreateApplication); IApplication CreateApplication() { ... } </code> <h>Stage 5</h> Every application has certain tasks to execute during shutdown. For example, an application will want to close down any open connections to external resources, close file (especially log files) and perhaps inform the user of shutdown. Instead of exposing a specific "shutdown" method, a Quino 2.0 application can simply be disposed to shut it down. If you use <c>ApplicationManager.Run()</c> as shown above, then you're already sorted---the application will be disposed and the user will be informed in case of catastrophic failure; otherwise, you can shut down and get the final application transcript from the disposed object. <code> application.Dispose(); var transcript = application.GetTranscript(); // Do something with the transcript... </code> <h>Stage 2 Redux</h> We're finally ready to discuss stage 2 in detail. An IOC has two phases: in the first phase, the application <i>registers</i> services with the IOC; in the second phase, the application <i>uses</i> services from the IOC. An application should <i>use</i> the IOC as much as possible, so Quino keeps stage 2 as short as possible. Because it can't use the IOC during the registration phase, code that runs in this stage shares objects via a poor-man's IOC built into the <c>IApplication</c> that allows modification and only supports singletons. Luckily, very little end-developer application code will ever need to run in this stage. It's nevertheless interesting to know how it works. Obviously, any code in this stage that <i>uses</i> the IOC will cause it to switch from phase one to phase two and subsequent attempts to register services will fail. Therefore, while application code in stage 2 has to be careful, you don't have to worry about <i>not</i> knowing you've screwed up. Why would we have this stage? Some advocates of using an IOC claim that everything should be configured in code. However, it's not uncommon for applications to want to run very differently based on command-line or other configuration parameters. The Quino startup handles this by placing the following actions in stage 2: <ul> Parse and apply command-line Import and apply external configuration (e.g. from file) </ul> An application is free to insert more actions before the <c>ServicesInitialized</c> action, but they have to play by the rules outlined above. <h>"Single" objects</h> Code in stage 2 shares objects by calling <c>SetSingle()</c> and <c>GetSingle()</c>. There are only a few objects that fall into this category. The calls <c>UseCore()</c> and <c>UseApplication()</c> register most of the standard objects used in stage 2. Actually, while they're <i>mostly</i> used during stage 2, some of them are also added to the poor man's IOC in case of catastrophic failure, in which case the IOC cannot be assumed to be available. A good example is the <c>IApplicationCrashReporter</c>. <h>Executing Stages</h> Before listing all of the objects, let's take a rough look at how a standard application is started. The following steps outline what we consider to be a good minimum level of support for any application. Of course, the Quino configuration is modular, so you can take as much or as little as you like, but while you can use a naked <c>Application</c>---which has absolutely <i>nothing</i> registered---and you can call <c>UseCore()</c> to have a bit more---it registers a handful of low-level services but no actions---we recommend calling at least <c>UseApplication()</c> to adds most of the functionality outlined below. <ol> <b>Create application</b>: This involves creating the IOC and most of the IOC registration as well as adding most of the application startup actions (stage 1) <b>Set debug mode</b>: Get the final value of <c>RunMode</c> from the <c>IRunSettings</c> to determine if the application should catch all exceptions or let them go to the debugger. This involves getting the <c>IRunSettings</c> from the application and getting the final value using the <c>IApplicationManagerPreRunFinalizer</c>. This is commonly an implementation that can allows setting the value of <c>RunMode</c> from the command-line in debug builds. This further depends on the <c>ICommandSetManager</c> (which depends on the <c>IValueTools</c>) and possibly the <c>ICommandLineSettings</c> (to set the <c>CommandLineConfigurationFilename</c> if it was set by the user). <b>Process command line</b>: Set the <c>ICommandProcessingResult</c>, possibly setting other values and adding other configuration steps to the list of startup actions (e.g. many command-line options are switches that are handled by calling <c>Configure<tsettings>()</c> where <c>TSettings</c> is the configuration object in the IOC to modify). <b>Read configuration file</b>: Load the configuration data into the <c>IConfigurationDataSettings</c>, involving the <c>ILocationManager</c> to find configuration files and the <c>ITextValueNodeReader</c> to read them. The <c>ILogger</c> is used throughout by various actions to log application behavior If there is an unhandled error, the <c>IApplicationCrashReporter</c> uses the <c>IFeedback</c> or the <c>ILogger</c> to notify the user and log the error The <c>IInMemoryLogger</c> is used to include all in-memory messages in the <c>IApplicationTranscript</c> </ol> The next section provides detail to each of the individual objects referenced in the workflow above. <h>Available Objects</h> You can get any one of these objects from the <c>IApplication</c> in at least two ways, either by using <c>GetSingle<tservice>()</c> (safe in all situations) or <c>GetInstance<tservice>()</c> (safe only in stage 3 or later) or there's almost always a method which starts with "Use" and ends in the service name. The example below shows how to get the <c>ICommandSetManager</c><fn> if you need it. <code> application.GetCommandSetManager(); application.GetSingle<icommandsetmanager>(); // Prefer the one above application.GetInstance<icommandsetmanager>(); </code> All three calls return the exact same object, though. The first two from the poor-man's IOC; the last from the real IOC. Only applications that need access to low-level objects or need to mess around in stage 2 need to know which objects are available where and when. Most applications don't care and will just always use <c>GetInstance()</c>. The objects in the poor-man's IOC are listed below. <h level="3">Core</h> <ul> <c>IValueTools</c>: converts values; used by the command-line parser, mostly to translate enumerate values and flags <c>ILocationManager</c>: an object that manages aliases for file-system locations, like "Configuration", from which configuration files should be loaded or "UserConfiguration" where user-specific overlay configuration files are stored; used by the configuration loader <c>ILogger</c>: a reference to the main logger for the application <c>IInMemoryLogger</c>: a reference to an in-memory message store for the logger (used by the <c>ApplicationManager</c> to retrieve the message log from a crashed application) <c>IMessageFormatter</c>: a reference to the object that formats messages for the logger </ul> <h level="3">Command line</h> <ul> <c>ICommandSetManager</c>: sets the schema for a command line; used by the command-line parser <c>ICommandProcessingResult</c>: contains the result of having processed the command line <c>ICommandLineSettings</c>: defines the properties needed to process the command line (e.g. the <c>Arguments</c> and <c>CommandLineConfigurationFilename</c>, which indicates the optional filename to use for configuration in addition to the standard ones) </ul> <h level="3">Configuration</h> <ul> <c>IConfigurationDataSettings</c>: defines the <c>ConfigurationData</c> which is the hierarchical representation of all configuration data for the application as well as the <c>MainConfigurationFilename</c> from which this data is read; used by the configuration-loader <c>ITextValueNodeReader</c>: the object that knows how to read <c>ConfigurationData</c> from the file formats supported by the application<fn>; used by the configuration-loader </ul> <h level="3">Run</h> <ul> <c>IRunSettings</c>: an object that manages the <c>RunMode</c> ("release" or "debug"), which can be set from the command line and is used by the <c>ApplicationManager</c> to determine whether to use global exception-handling <c>IApplicationManagerPreRunFinalizer</c>: a reference to an object that applies any options from the command line <i>before</i> the decision of whether to execute in release or debug mode is taken. <c>IApplicationCrashReporter</c>: used by the <c>ApplicationManager</c> in the code <i>surrounding</i> the entire application execution and therefore not guaranteed to have a usable IOC available <c>IApplicationDescription</c>: used together with the <c>ILocationManager</c> to set application-specific aliases to user-configuration folders (e.g. <c>AppData\{CompanyTitle}\{ApplicationTitle}</c>) <c>IApplicationTranscript</c>: an object that records the last result of having run the application; returned by the <c>ApplicationManager</c> after <c>Run()</c> has completed, but also available through the application object returned by <c>CreateAndStartUp()</c> to indicate the state of the application after startup. </ul> Each of these objects has a very compact interface and has a single responsibility. An application can easily replace any of these objects by calling <c>UseSingle()</c> during stage 1 or 2. This call sets the object in both the poor-man's IOC as well as the real one. For those rare cases where a non-IOC singleton needs to be set after the IOC has been finalized, the application can call <c>SetSingle()</c>, which does not touch the IOC. This feature is currently used only to set the <c>IApplicationTranscript</c>, which needs to happen even after the IOC registration is complete. <hr> <ft>Two large customer solutions, two medium-sized internal solutions (Punchclock and JobVortex) as well as the Demo/Sandbox solution. These solutions include the gamut of application types: <ul> 3 ASP.NET MVC applications 2 ASP.NET WebAPI applications 2 Windows services 3 Winform/DevExpress applications 2 Winform/DevExpress utilities 4 Console applications and utilities </ul></ft> <ft>I originally used <c>ITextValueNodeReader</c> as an example, but that's one case where the recommended call doesn't match 1-to-1 with the interface name. <code> application.GetSingle<itextvaluenodereader>(); application.GetInstance<itextvaluenodereader>(); application.GetConfigurationDataReader(); <hl>// Recommended</hl> </code></ft> <ft>Currently only XML, but <a href="">JSON</a> is on the way when someone gets a free afternoon.</ft>