OverDrive
Added by Ted Husted, last edited by Ted Husted on May 20, 2005
Labels: 
(None)


OverDrive Preamble

In the popular Struts tradition, OverDrive applications rely on an external configuration file to define what we want to do, and how we want to do it. Struts describes the application in terms of web-centric "Actions". OverDrive applications describe themselves in terms of business-centric Commands, independant of the web platform.

In an OverDrive application, the presentation layer (be it ASP.NET, Struts Classic, JavaServer Faces, et cetera) harvest input and pass it to the appropriate Command. The Command does all the heavy lifting and passes back the outcome. If things go sour, the Command passes back messages ready to be displayed to the client.

Both Actions and Commands do the same sort of thing: validate input, tender messages, convert data, process logic, and format output.

Compared to Actions, the major difference is that Commands are strongly cohesive and loosely coupled. Commands can be chained so that it's easy for each request to map to a single, cohesive Chain of Commands. And, because Commands can be chained, they can also be finely-grained and loosely coupled. The same Command can appear as a "link" in any number of Command chains.

Another major difference between Commands and Actions is that Commands specify which attributes are related and required to process the Command. In Struts Classic, the "field manifest" is implied by the ActionForm and Validator configuration. In an OverDrive application, the inputs and outputs are explicit.

Commands

Here's some example Command elements:

<object id="step_edit" type="WNE.Model.Commands.BaseView">
   <property name="ID"><value>step_edit</value></property>
   <property name="AddCommands">
   <list>
   <ref object="step_select"/>
   <ref object="step_view"/>
   </list>
   </property>
   </object>
   
   <object id="step_select" type="WNE.Model.Commands.BaseSelect">
   <property name="ID"><value>step_select</value></property>
   <property name="RelatedIDs">
   <list>
   <value>complete_date</value>
   <value>due_date</value>
   <value>ticket_event_notes</value>
   <value>event_key</value>
   <value>staff_key</value>
   <value>ticket_event_ref</value>
   <value>facility_ticket_key</value>
   <value>ticket_event_key</value>
   </list>
   </property>
   <property name="RequiredIDs">
   <list>
   <value>ticket_event_key</value>
   </list>
   </property>
   </object>
   
   <object id="step_view" type="WNE.Model.Commands.BaseView">
   <property name="ID"><value>step_view</value></property>
   <property name="AddCommands">
   <list>
   <ref object="infraction_select"/>
   <ref object="ticket_select"/>
   <ref object="facility_select"/>
   <ref object="event_key_list"/>
   <ref object="staff_key_list"/>
   <ref object="format_output"/>
   </list>
   </property>
   </object>
   
   <object id="step_save_with_validate" type="WNE.Model.Commands.BaseChain">
   <property name="ID"><value>step_save_with_validate</value></property>
   <property name="AddCommands">
   <list>
   <ref object="convert_input"/>
   <ref object="step_save"/>
   </list>
   </property>
   </object>
   
   <object id="step_save" type="WNE.Model.Commands.BaseSave">
   <property name="ID"><value>step_save</value></property>
   <property name="KeyID"><value>ticket_event_key</value></property>
   <property name="InsertID"><value>step_insert</value></property>
   <property name="UpdateID"><value>step_update</value></property>
   <property name="RelatedIDs">
   <list>
   <value>due_date</value>
   <value>complete_date</value>
   <value>ticket_event_notes</value>
   <value>ticket_event_key</value>
   </list>
   </property>
   <property name="RequiredIDs">
   <list>
   <value>event_key</value>
   <value>staff_key</value>
   <value>facility_ticket_key</value>
   </list>
   </property>
   </object>

(These elements use Spring, but another IOC container could be used.)

FieldTable

More detail about the fields can be provided in the "FieldTable". The FieldTable provides background information on whatever fields an application uses. The target datatype, validation messages, labels, hints, can all be specified in the FieldTable.

Like Commands, the FieldTable can be specified as XML elements. Ordinary String fields don't need to be specified, but numeric and date types do. Here's a FieldTable that specifies a Date field:

<object id="FieldTable" type="Agility.Nexus.FieldTable">
   <property name="AddFields">
   <list>
   <ref object="ticket_date"/>
   </list>
   </property>
   </object>
   
   <!-- Declare the Date fields. "d" is .NET for "short date" -->
   <!-- TODO: Setup a DateFieldContext to default this stuff -->
   
   <object id="ticket_date" type="Agility.Nexus.FieldContext">
   <property name="ID"><value>ticket_date</value></property>
   <property name="Alert"><value>{0} must be a valid date</value></property>
   <property name="DataFormat"><value>d</value></property>
   <property name="DataTypeName"><value>System.DateTime</value></property>
   </object>

Between the field lists in the Commands, and the background data in the FieldTable, OverDrive applications can delegate routine validation tasks to standard Commands. The presentation layer need only collect values and display any messages returned.

The error messaging system also follows the popular Struts tradition. Messages are provided as a keyed list of lists. Any number of messages can be added for a key, which is usually the field name. The presentation layer can then loop through the message list for a particular key, or for all the keys, as it pleases.

If an OverDrive application passes up a Locale object with a request, the messages can be returned already localized and ready to render: just add markup!

When localization is being used, the Alert and other message fields become keys to a secondary messaging resources. An element might then look like:

<object id="ticket_date" type="Agility.Nexus.FieldContext">
   <property name="ID"><value>ticket_date</value></property>
   <property name="Alert"><value>date_required</value></property>
   <property name="DataFormat"><value>d</value></property>
   <property name="DataTypeName"><value>System.DateTime</value></property>
   </object>

ViewHelper

On the presentation layer, an OverDrive application can use simple ViewHelper classes to integrate with the MVC framework.

Unable to find source-code formatter for language: c#. Available languages are: actionscript, html, java, javascript, none, sql, xhtml, xml
private void FacilityFind_Submit(object sender, EventArgs e)
   {
   IViewHelper helper = new ViewHelper();
   helper.Controller = MyController.Instance();
   IRequestContext context = helper.GetContext (App.FACILITY_LIST);
   View.ReadControls(pnlFacilityFind.Controls, context);
   helper.Execute();
   if (helper.IsNominal())  {
   pnlFacilityFind.Visible = false;
   FacilityList_Load(helper);
   }
   else
   {
   Error_Load(helper);
   }
   
   private void FacilityList_Load(FacilityListHelper helper)
   {
   repFacilityList.DataSource = helper.FacilityList;
   repFacilityList.DataBind();
   Title_Load(msg_FACILITY_LIST_TITLE);
   Prompt_Load(msg_FACILITY_LIST);
   pnlFacilityList.Visible = true;
   }

OverDrive Mission Statement

Create, deploy, and improve a range of best-practice example applications, and help others use the same techniques to create, deploy, and improve their own applications.

Applications rule, frameworks drool.

Most application frameworks ship with one or more example applications. Struts has MailReader. iBATIS has Petstore. JSF has CarDemo. ASP.NET distributes several "starter kits". Some frameworks, like Spring, even consider the examples to be "first-class citizens".

From the OverDrive perspective, the applications are not just first-class citizens, they are the only first-class citizens. The MVC framework shared by the applications is simply a means to an end.

OverDrive is about writing business applications, regardless of platform, and extracting components the applications can share. The initial OverDrive applications are being written for ASP.NET/Mono, but versions for Java5 and PHP5 are expected.

The first two applications on the billet are

  • PhoneBook – A single-table employee directory.
  • MailReader – A multi-table account listing.

Once these ship, others will follow, including

  • Examples – A coding reference to document common strategies.
  • Wicker - A shopping cart application.
  • Gavel - An online b2c auction application.

The MVC Framework behind the OverDrive applications bundles two major components:

  • Agility, a C# port of Commons Chain of Responsibilty.
  • Nexus, an application controller built over Agility.

The applications share the Nexus application controller and utilities.

Agility.Core

Agility Core is an implementation of the classic Chain of Command and Context patterns.

Each Command is a separate object, sharing a common interface, with a single Execute method. Commands can be combined into Chains and executed as a single unit of work (Composite pattern). A Command can be used by more than one Chain, encouraging reuse.

A Context is passed to the Command (or Chain of Commands) to store the input and output values. A standard IContext is simply a IDictionary (or Map) – though subclasses may provide special features.

Nexus.Core

Nexus is an application controller based on Agility Core that provides a set of helper classes, ready to use in OverDrive applications, including RequestContext, RequestCommand, and FieldContext.

RequestContext helps us exchange data between the business and presentation layers. RequestContext predefines several properties for storing input, output, messages, and other common attributes, including Locale (or Culture).

RequestCommand offers an alternate signature for use with a RequestContext and predefines properties to make it easier to validate the Context and execute the Command.

FieldTable is a manifest of the controls used by our application. The table includes a few helper methods, but it is mainly a collection of FieldContext entries.

FieldContext predefines several properties common to controls, including Alert, Constraints, ControlType, and DataType. The Nexus validation command uses the FieldContext properties to verify input. The FieldContext entries are made available through a FieldTable.

RequestController helps us find the Context and Command objects used by an application. Commands are keyed keyed by a logical name. Using a Controller means the caller can access the Command by its logical name not by its classname (Dependency Injection pattern).

ViewHelper is a facade around an IRequestContext. It can be used to expose to a page only those methods that concern a page. It also adds a convenient Execute method for running the associated Command.

Nexus.Extra

In the Nexus Core package, some members are interfaces without base implementations. The Extra package provides added-value implemenmtations that might come with extra dependendencies.

For example, the Spring.RequestController class is Spring-Aware. If you use the Spring RequestController, you can create NexusCommands by specifying them in a standard Spring configuration file.

Other Nexus Extras include interfaces and base classes that are not needed by Nexus Core but that are implemented by multiple OverDrive applications.

The last mile

AppContext is an optional IContext class that we can extend with specific presentation properties in each application's namespace. An AppContext combines the best features of a IDictionary and a Property object. We can expose the attributes our pages display as Properties but store the values as an IDictionary. The Properties work well with page controls. The IDictionary works well with persistence utilities, loops, and collections.

Since it is meant to be used with the presentation layer, as a rule, an AppContext will store only String or IList properties.

Aside from database fields, we can add whatever calculated properies we may need to display. For example, a "DirectoryName" property might be the concaternation of a "FirstName" and a "LastName" property. There might not be an actual "DirectoryName" field stored in the IDictionary. The property method can render the value on the fly.

OverDrive applications use a "coarsely-grained" AppContext. For smaller applications, with say, up to a hundred display properties, we might use a single AppContext. If a page doesn't use a property, then it's null, no harm done. Larger applications might want to use AppContexts that align with major workflows. A planned enhancement is to generate the code for a standard AppContext from the FieldTable.

The idea is that AppContext represents a coherent set of fields used by a set of related pages. A database guru might call this a "normalized view" of the fields needed by the pages. The key principle to observe is DRY - Don't Repeat Yourself. Try to define the display property once on a coarsely-grained AppContext, so that we can use (and reuse) it wherever we need it.

Since the AppContext is a controller class, and is not bound to a user interface library, the same AppContext instance can be used with a web page or a PDF report or a desktop GUI, as needed.


Agility (-)
Nexus <*>

(To be continued)


Site running on a free Atlassian Confluence Open Source Project License granted to OSS. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.5.5 Build:#811 Jul 25, 2007) - Bug/feature request - Contact Administrators