Published by Marco on 27. Nov 2015 15:49:34
Updated by Marco on 2. Dec 2015 06:02:17
------------------------------------------------------------------------

As part of the final release process for Quino 2, we've upgraded 5 solutions [1]
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 "API
Design: Running an Application (Part I)"
 and "API Design: To
Generic or not Generic? (Part II)"
 as well as the three-part
series that starts with "Encodo’s configuration library for Quino: part I"
.

[Quino Execution Stages]

The life-cycle of a Quino 2.0 application breaks down into roughly the following
stages:

   1. Build Application: Register services with the IOC, add objects needed
      during configuration and add actions to the startup and shutdown lists
   2. Load User Configuration: 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 ServicesInitialized
   3. Apply Application Configuration: Apply code-based configuration to IOC
      objects; ends with the ServicesConfigured action
   4. Execute: execute the loop, event-handler, etc.
   5. Shut Down: dispose of the application, shutting down services in the IOC,
      setting the exit code, etc.

[Stage 1]

The first stage is all about putting the application together with calls to Use
various services and features. This stage is covered in detail in three parts,
starting with "Encodo’s configuration library for Quino: part I"
.

[Stage 2]

Let's tackle this one last because it requires a bit more explanation.

[Stage 3]

Technically, an application can add code to this stage by adding an
IApplicationAction before the ServicesConfigured action. Use the
Configure() extension method in stage 1 to configure individual
services, as shown below.


application.Configure(
  s => s.Behavior = FileLogBehavior.MultipleFiles
);

[Stage 4]

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 Run method, which called by the
ApplicationManager after the application has started.


var transcript = new ApplicationManager().Run(CreateApplication, Run);

IApplication CreateApplication() { ... }
void Run(IApplication application) { ... }

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 Run,
you'll call CreateAndStartupUp.


var application = new ApplicationManager().CreateAndStartUp(CreateApplication);

IApplication CreateApplication() { ... }

[Stage 5]

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 ApplicationManager.Run() 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.


application.Dispose();
var transcript = application.GetTranscript();
// Do something with the transcript...

[Stage 2 Redux]

We're finally ready to discuss stage 2 in detail.

An IOC has two phases: in the first phase, the application registers services
with the IOC; in the second phase, the application uses services from the IOC.

An application should use 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
IApplication 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 uses 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 not 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:

  * Parse and apply command-line
  * Import and apply external configuration (e.g. from file)

An application is free to insert more actions before the ServicesInitialized
action, but they have to play by the rules outlined above.

["Single" objects]

Code in stage 2 shares objects by calling SetSingle() and GetSingle(). There are
only a few objects that fall into this category. 

The calls UseCore() and UseApplication() register most of the standard objects
used in stage 2. Actually, while they're mostly 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
IApplicationCrashReporter.

[Executing Stages]

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 Application -- which has absolutely nothing registered
-- and you can call UseCore() to have a bit more -- it registers a handful of
low-level services but no actions -- we recommend calling at least
UseApplication() to adds most of the functionality outlined below.

   1. Create application: This involves creating the IOC and most of the IOC
      registration as well as adding most of the application startup actions
      (stage 1)
   2. Set debug mode: Get the final value of RunMode from the IRunSettings to
      determine if the application should catch all exceptions or let them go to
      the debugger. This involves getting the IRunSettings from the application
      and getting the final value using the IApplicationManagerPreRunFinalizer.
      This is commonly an implementation that can allows setting the value of
      RunMode from the command-line in debug builds. This further depends on the
      ICommandSetManager (which depends on the IValueTools) and possibly the
      ICommandLineSettings (to set the CommandLineConfigurationFilename if it
      was set by the user).
   3. Process command line: Set the ICommandProcessingResult, 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 Configure() where TSettings is the configuration object
      in the IOC to modify).
   4. Read configuration file: Load the configuration data into the
      IConfigurationDataSettings, involving the ILocationManager to find
      configuration files and the ITextValueNodeReader to read them.
   5. The ILogger is used throughout by various actions to log application
      behavior
   6. If there is an unhandled error, the IApplicationCrashReporter uses the
      IFeedback or the ILogger to notify the user and log the error
   7. The IInMemoryLogger is used to include all in-memory messages in the
      IApplicationTranscript

The next section provides detail to each of the individual objects referenced in
the workflow above.

[Available Objects]

You can get any one of these objects from the IApplication in at least two ways,
either by using GetSingle() (safe in all situations) or
GetInstance() (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 ICommandSetManager [2] if you need it.


application.GetCommandSetManager();
application.GetSingle(); // Prefer the one above
application.GetInstance();

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 GetInstance().

The objects in the poor-man's IOC are listed below.

[Core]

  * IValueTools: converts values; used by the command-line parser, mostly to
    translate enumerate values and flags
  * ILocationManager: 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
  * ILogger: a reference to the main logger for the application
  * IInMemoryLogger: a reference to an in-memory message store for the logger
    (used by the ApplicationManager to retrieve the message log from a crashed
    application)
  * IMessageFormatter: a reference to the object that formats messages for the
    logger

[Command line]

  * ICommandSetManager: sets the schema for a command line; used by the
    command-line parser
  * ICommandProcessingResult: contains the result of having processed the
    command line
  * ICommandLineSettings: defines the properties needed to process the command
    line (e.g. the Arguments and CommandLineConfigurationFilename, which
    indicates the optional filename to use for configuration in addition to the
    standard ones)

[Configuration]

  * IConfigurationDataSettings: defines the ConfigurationData which is the
    hierarchical representation of all configuration data for the application as
    well as the MainConfigurationFilename from which this data is read; used by
    the configuration-loader
  * ITextValueNodeReader: the object that knows how to read ConfigurationData
    from the file formats supported by the application [3]; used by the
    configuration-loader

[Run]

  * IRunSettings: an object that manages the RunMode ("release" or "debug"),
    which can be set from the command line and is used by the ApplicationManager
    to determine whether to use global exception-handling
  * IApplicationManagerPreRunFinalizer: a reference to an object that applies
    any options from the command line before the decision of whether to execute
    in release or debug mode is taken.
  * IApplicationCrashReporter: used by the ApplicationManager in the code
    surrounding the entire application execution and therefore not guaranteed to
    have a usable IOC available
  * IApplicationDescription: used together with the ILocationManager to set
    application-specific aliases to user-configuration folders (e.g.
    AppData\{CompanyTitle}\{ApplicationTitle})
  * IApplicationTranscript: an object that records the last result of having run
    the application; returned by the ApplicationManager after Run() has
    completed, but also available through the application object returned by
    CreateAndStartUp() to indicate the state of the application after startup.

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 UseSingle() 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 SetSingle(), which does not touch the IOC. This feature is currently used
only to set the IApplicationTranscript, which needs to happen even after the IOC
registration is complete.

--------------------------------------------------------------------------------


[1] 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:
  
     * 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


[1] I originally used ITextValueNodeReader as an example, but that's one case
    where the recommended call doesn't match 1-to-1 with the interface name.
  
   application.GetSingle();
   application.GetInstance();
   application.GetConfigurationDataReader(); // Recommended


[1] Currently only XML, but "JSON"
     is on the way when someone
    gets a free afternoon.