OverDrive
Added by Ted Husted, last edited by Ted Husted on Oct 13, 2005  (view change)
Labels: 
(None)


Nexus: A practice-based framework for business applications

In Code Complete, Steven McConnell advises us to program into a platform, rather than with a platform. Regardless of what conveniences a platform tries to provide, we should first follow our own best practices. Books have been filled with the best practices available to developers today. But, of all of these, we believe that the two best, best-practices are:

  • Don't Repeat Yourself, and
  • Separate Concerns

"Don't Repeat Yourself"

"DRY says that every piece of system knowledge should have one authoritative, unambiguous representation. Every piece of knowledge in the development of something should have a single representation. A system's knowledge is far broader than just its code. It refers to database schemas, test plans, the build system, even documentation.

"Given all this knowledge, why should you find one way to represent each feature? The obvious answer is, if you have more than one way to express the same thing, at some point the two or three different representations will most likely fall out of step with each other. Even if they don't, you're guaranteeing yourself the headache of maintaining them in parallel whenever a change occurs. And change will occur. DRY is important if you want flexible and maintainable software. "

Dave Thomas, "All programming is maintenance programming", 10 March 2003.

"Separate Concerns"

"Let me try to explain to you, what to my taste is characteristic for all intelligent thinking. It is, that one is willing to study in depth an aspect of one's subject matter in isolation for the sake of its own consistency, all the time knowing that one is occupying oneself only with one of the aspects.

"We know that a program must be correct and we can study it from that viewpoint only; we also know that is should be efficient and we can study its efficiency on another day (...) But nothing is gained - on the contrary - by tackling these various aspects simultaneously. It is what I sometimes have called 'the separation of concerns' (...)"

E.W. Dijkstra, "On the role of scientific thought", 30 Aug 1974, Neuen, The Netherlands.


The mission of Nexus is to provide a business application framework that lets us program into a platform – isolating aspects so that every fact can have an authoritative source.

Nexus does not replace presentation frameworks, like ASP.NET or Struts. Nexus provides the missing link between presentation frameworks and the business logic that drives your application.

Since Nexus promotes a layered architecture, it should be no surprise that Nexus builds on another framework, called Agility.

Agility: Our solid foundation

Agility is an implementation of the classic Chain of Responsibility, Command, and Context patterns. The code was originally written for Java, and then ported to C# as part of the OverDrive project.

"Chain of Responsibility pattern"

"Avoid coupling the sender of a request to its receivere by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it."

Design Patterns by Gamma, Helm, Johnson, and Vlissides (ISBN 0201633612).

"Command pattern"

"Encapsulate a request as an object, thereby letting you parameterize clients with different request, queue or log requests, and support undoable operations"

Design Patterns by Gamma, Helm, Johnson, and Vlissides (ISBN 0201633612).

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.

"Composite pattern"

"Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat indivividual objects and compositions of objects uniformly"

Design Patterns by Gamma, Helm, Johnson, and Vlissides (ISBN 0201633612).

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.

The scope of the Agility framework is to provide a classic implementation of the Chain of Responsibility pattern described in Design Patterns (ISBN 0201633612). The Nexus framework extends the Agility Command, Context, and Catalog members, and adds new members to the core group: FieldTable, ViewHelper, and Processor.

The Agility framework utilizes a chain-based architecture, defined in the following way:
  • Application logic is accessed through commands.
  • A context provides runtime values to commands.
  • A command can be a chain.
  • A chain can contain commands, including other chains.
  • A command is invoked by passing a context which can be shared by every command in a chain.

The basic idiom: Ties that bind

The essential use case in any business application is exchanging dynamic values with a user interface and coping with any errors that might occur along the way.

Binding

Here is a typical Nexus idiom for populating a form and handling errors:

IViewHelper h = FindHelper;
   h.ExecuteBind (panel.Controls);
   bool ok = (h.IsNominal);
   if (!ok)
   Page_Error = h;

Reading

Here is a typical idiom for reading input from a form and handling errors:

IViewHelper h = ListHelper;
   h.ExecuteRead (panel.Controls);
   bool ok = (h.IsNominal);
   if (!ok)
   Page_Error = h;
   else
   List_Load (h);

Like an iceberg, the business logic mechanics are hidden behind a smattering of calls from the UI layer.

The Nexus API: A motely crew

Nexus is an application controller based on Agility that provides a set of framework classes, ready to use in end-point applications. Nexus framework classes include RequestContext, RequestCommand, FieldTable, Processor, and ViewHelper.

Nexus.Core

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 a Context and execute its Command.
FieldTable is a manifest of the controls used by our application. The table entries (FieldContexts) define several optional properties, including a Processor for the field.
FieldContext predefines several properties common to controls, including Alert, Constraints, ControlType, and Processor. FieldContext entries are made available through the FieldTable.
Processors handle the conversion and/or formatting for a kind of field.
RequestCatalog helps us find the Context and Command objects used by an application.
ViewHelper is a facade around a Command and Context. It exposes to a page only those methods that concern a page.

Nexus Core Group: Who? Who are you?

Let's walk through the key members of the Nexus core group and introduce each in turn.

  • ViewHelper
  • Context
  • Catalog
  • Command
  • FieldTable
  • Processor

ViewHelper [Nexus.Core.Helpers.IViewHelper]

An vital aspect of business applications is that they must interact with a user interface layer (UI layer). We want business applications to be powerful and flexible, but we also want them to be easily controlled from a user interface.

To simplify using Nexus from a UI layer, we provide a facade that makes it easy to pass values and invoke commands. As far as the UI layer knows, this facade, the ViewHelper, is the framework.

Criteria [Nexus.Core.RequestContext.Criteria, Nexus.Core.Helpers.ViewHelper.Criteria]

Most often, a UI layer will collect string values. Many string values need to be validated and converted to binary data types, like DateTime or Integer. The ViewHelper provides a Criteria property that can be used as an input and output buffer. Behind the scenes, incoming values are vetted, formatted, and converted. Outgoing values are also converted and formatted. But all the UI layer has to do is pass the values, invoke the command, and check the result. The UI layer passes strings up, and gets strings back.

To further simplify integrating UI layers with helpers, Nexus provides a set of User Control classes: ViewControl, FindControl, and GridControl. Unlike the Core classes, the User Controls classes are tied to the ASP.NET framework. Accordingly, the controls are kept in another namespace, Nexus.Web.

ViewControl [Nexus.Web.IViewControl, Nexus.Web.ViewControl]

The View*Control* provides a direct link between the UI layer and the View*Helper*. In turn, the View*Helper* provides a link to the business logic and data access layers. The primary goals of the ViewControl are to

  • Populate controls with dynamic values
  • Harvest input values from controls
  • Provide access to the appropriate Helper to process values

The UI layer can use the ViewControl as the base for its own controls. The controls then have instant access to the Nexus API. The custom controls can invoke Nexus API methods automatically as part of the control's normal lifecycle.

FindControl [Nexus.Web.IFindControl, Nexus.Web.FindControl]

Often, we will use a control to input search criteria, and then use that criteria to filter a list of matching entries. The FindControl provides a standard property to store the name of the input command. The command name can be injected when the control initializes and then called automatically later in the lifecycle.

GridControl [Nexus.Web.IGridControl, Nexus.Web.GridControl]

DataGrids are a wonderful tool, but programming a DataGrid is still a lot of work. The base GridConrol object makes it easier to use DataGrids with Nexus Helpers. Among other things, the GridControl provides standard properties for List and Edit command. Once the command is set, the control can automatically invoke the command at the appropriate times.

The GridControl is also designed to be easy to program from the codebehind. From the codebehind, we can set the columns and column headings dynamically, and indicate whether a link command or edit command are wanted. The HTML styles for the DataGrid can still be set from the ASPX page. It's easy to create a base GridControl that sets the markup style, and then extend that control as needed to define the content. The GridControl makes it very easy to separate DataGrid markup from DataGrid content (the way Anders intended).

Context [Nexus.Core.IRequestContext]

An important role of the ViewHelper is to restrict access to the Nexus Context. When the UI passes values to the Criteria, they are stored in a special holding area within the Context. As input values are validated, formatted, and converted, they travel from the Critiera to the main Context. Likewise, as output values are converted and formatted, they travel from the main Context to the Criteria. But, the machinery is hidden from the UI. All the UI sees is the Critiera, which accepts and tenders formatted strings.

The Context stores the input and output data in its native form, along with references to the other Nexus members. The Context is the framework's "company store". Anything you can get from Nexus, you can get from the Context.

The Context is considered a "trusted" zone. Values must be validated before being placed in the Context. When a Command executes against the Context, it can assume that the Context will contain all required values and that all the values are reasonable.

Within the framework, the concern of validation is separated from the concern of execution. The ViewHelper and Catalog work together to validate the Criteria, before passing values to the Context and invoking the Command. Once the Command is invoked, it can run optimistically. Of course, even then, the framework does catch and return all exceptions as a fault message. Even in the trusted zone, Command failures are graceful.

Catalog [Nexus.Core.IRequestCatalog, Nexus.Extras.Spring.RequestCatalog]

At runtime, all of the core Nexus members – Commands, Contexts, Helpers, Processors, and the FieldTable – can be obtained from a Catalog. The Catalog is created when the application loads. Internally, the Catalog is an object graph containing singleton or non-singleton instances of the Nexus members used by an application. The Catalog can be created from an XML document using an object factory component, like Spring).

"Object Factory pattern"

Abstract Factory "Provide an interface for creating families of related or dependent objects without specifying their concrete classes."

Design Patterns by Gamma, Helm, Johnson, and Vlissides (ISBN 0201633612).

Members are keyed by a logical name. Using a Catalog means the caller can access the Command by a logical name, rather than by an esoteric classname. The Catalog makes shared resources, like the FieldTable, available to every Context. The Catalog can also ensure that standard operations are carried out on each Command invocation (e.g., request).

The UI layer can ask for a Helper by the logical name we assign in the Catalog.

Often, a command can be invoked, and set of controls in a UI form can be read or bound, with a single call to the Helper interface:

  • Helper.ReadExecute(form.Controls);
  • Helper.ExecuteBind(form.Controls);

This code can read or bind whatever members are in the form's collection of Controls.

But, how would a page author know which values need to be placed on the form? If the code-behind reads them in from a collection, how do we know what values the Command expects? For these details, we can go straight to "the horse's mouth", and ask the Nexus Command.

Command [Nexus.Core.IRequestCommand]

Just as the Helper is the framework to the ASPX code-behind, the Command is the framework to the ASPX template.

Each IRequestCommand can specify the fields it requires as well as other related, or optional, fields. For example, this RequestCommand requires a key value and outputs a directory entry.

<object id="entry" type="PhoneBook.Core.Commands.BaseEntry, PhoneBook.Core">
   <property name="ID">
   <value>entry</value>
   </property>
   <property name="RequiredIDs">
   <list>
   <value>entry_key</value>
   </list>
   </property>
   <property name="OutputIDs">
   <list>
   <value>last_name</value>
   <value>first_name</value>
   <value>extension</value>
   <value>user_name</value>
   <value>hired</value>
   <value>hours</value>
   <value>editor</value>
   </list>
   </property>
   </object>

When coding a page, the author can refer to the input elements (required and related) and the output element of the command. If the command does not specify a field, then the command does not – and cannot – use the field. When the ViewHelper invokes the command, only the input and output fields specified are processed. Others fields are ignored (with Borg-like aplomb).

But what type of controls should we use to collect or display the values? What is the underlying datatype? What label should be used to describe the control? What hints should be displayed to aid data entry? What alert should display if there is a validation error?

These questions are answered by the Nexus FieldTable.

FieldTable [Nexus.Core.IFieldTable, Nexus.Extras.Spring.FieldTable]

The FieldTable specifies each value used by the application along with its control ID, label, hint, alert, and default control type. Like the command, the field table can be expressed in as a set of XML elements:

<object id="FieldTable" type="Nexus.Core.Tables.FieldTable">
   <property name="AddFieldContexts">
   <list>
   <ref object="last_name"/>
   <ref object="first_name"/>
   <ref object="extension"/>
   <ref object="user_name"/>
   <ref object="hired"/>
   <ref object="hours"/>
   <ref object="_entry_list"/>
   <ref object="_extension_list"/>
   <ref object="_hired_list"/>
   </list>
   </property>
   <property name="AddProcessors">
   <list>
   <ref object="DateTimeProcessor"/>
   <ref object="TelephoneProcessor"/>
   <ref object="EntryListProcessor"/>
   <ref object="ExtensionListProcessor"/>
   <ref object="HiredListProcessor"/>
   </list>
   </property>
   </object>

When a helper invokes a command, Nexus can use the FieldTable to verify the inputs. If required input is missing, or values are invalid, alerts can be posted to the context. The message store can hold any number of alerts, keyed to the offending fields.

If messages need to be localized, an alternate FieldTable implementation can be used which reads language tokens from a standard message resource. Even if your application is not being globalized, you might find it more convenient to place text messages in a resource rather than in the FieldTable configuration.

One such implementation is provided in the Nexus.Extras.Spring namespace. This implementation utilizes the {Spring.Web|http://springframework.net] message source interface to provide control labels and other messages.

The FieldTable for an application is registered with the Catalog, and a reference to the FieldTable is stored in every Context obtained from the Catalog.

Processors [Nexus.Core.Processors]

Through the FieldTable, we can specify one or more Processors for a field. Processors are responsible for converting input and formatting output. Processors for standard data types are (or will be) bundled with the framework.

Converter Processor Class
ArrayProcessor
BooleanProcessor
ByteProcessor
CharProcessor
CollectionProcessor
ComponentProcessor
CulturalInfoProcessor
DateTimeProcessor
DoubleProcoessor
EnumProcessor
Int16Processor
Int32Processor
Int64Processor
ReferenceProcessor
UInt16Processor
UInt32Processor
UInt64Processor
ColorProcessor
WebColorProcessor
FontProcessor

Albeit, there is often more to processing a field than observing the data type! A sequential value may need to be a specific range. A string token, like a telephone number, may need to be punctuated for display. A few standard validation Processors are (or will be) bundled with the framework to address the most common needs:

Validator Processor Class
RequiredFieldProcessor
CompareProcessor
RangeProcessor
RequiredExpressionProcessor
CustomProcessor
SummaryProcessor

Of course, for special needs, developers are encouraged to create and plugin custom Processors.

List Processor [Nexus.Core.Processors.IListProcessor]

A special kind of Processor is the List Processor. Many Commands return a list with rows of fields that need to be formatted. The List Processor iterates over the list and recurses into each entry, processing each field in the usual way.

pre-opt and post-opt Chains [Nexus.Core.Processors.ConverttInput, Nexus.Core.Processors.FormatOutput]

Usually, Processors fire as part of the Nexus post-op and pre-op Commands, which are usually Chains of Commands. The Nexus Catalog has pre-op and post-op properties that you can set from the XML configuration document, like the Commands and Helpers.

When the Helper invokes a Command, the Command is wrapped into a Chain. The pre-op Chain fires before the Helper's Command (or Chain of Commands), and the post-op Chain fires after the Helper's Command (if both the pre-op and Helper Commands are successful).

The default pre-opt Chain converts input, and the default post-opt Chain formats output. We can add whatever other commands an application needs to the pre-opt and post-op Chains, and even replace the default ConvertInput and FormatOutput commands bundled with the framework.

Nexus: The story so far ...

Used together the six key Nexus members:

  • ViewHelper
  • Context
  • Catalog
  • Command
  • FieldTable
  • Processor

provide a powerful, flexible, and testable framework for managing the business layer of an application.

Configuring Nexus: Start it up

Since Nexus is just a graph of objects, you could create all the members you need progmatically with the new operator.

  • A better way to create the Nexus object graph is declaratively using a XML configuration file.
  • An even better way to create the graph is to use an IOC container, like Spring.NET, injecting whatever settings and dependencies are needed into each object.

An effect of using dependency injection is that you tend to use fewer subclasses. Instead of subclassing to extend object behavior, it becomes easier to specialize behavior by injecting different properties to another instance.

"Dependency Injection"

Dependency Injection tools take the idea of an object factory a step further. Not only is a given object created, but any dependencies of the object are also created and set. The dependency may be a static value, or it may be another object in the Catalog. Any of the objects may be singletons or non-singletons.
When a helper is injected into a UI component, the component does not need to know the name of the Command it invokes. The component only needs to know which methods of the Helper interface to call. When the page runs, the helper is just there, like the "implicit" HTTP properties provided by the runtime web environment.

"singleton"

"Ensure a class only has one instance, and provide a global point of access to it."

Design Patterns by Gamma, Helm, Johnson, and Vlissides (ISBN 0201633612).

Spring.NET helps to encourage the base class pattern with its "parent" feature. An element can adopt settings from another element and specify only what settings need to be added or changed.

Parenting makes using XML elements much more like object-orientated programming. You can setup a base element, and then only specify the behavior that changes.

For command line applications, including unit tests, Nexus provides a utility singleton, Objects, to make loading Spring configurations easier. Objects.Factory() automatically loads an Objects.xml file. We can put all the object elements here, or we can use the import command to load other configuration files.

AppCore, AppBase, AppFields: The usual suspects

To initialize Nexus, we suggest using three standard files to help separate concerns:

AppCore.xml Nexus core members, like Catalog.
AppBase.xml Base parent elements used by an application.
AppFields.xml The fields table used by an application.

Often, the AppCore file can be reused from application to application. The other two will vary between applications.

In a larger application, we may find several catalog files, perhaps using file names that indicate what variety of objects the file creates. For example, an accounting application may have a files for journal.xml, receivable.xml, and payable.xml, instead of an omnibus catalog.xml file.

Business Logic and Data Access: Our dynamic duo

Nexus encapsulates business logic within a Command. The business logic may be embedded in the Command, or the Command might call another object to do the "heavy lifting".

Many applications separate business-logic operations form data-access operations. Separating the operations creates a distinct business logic layer (BLL), and a distinct data-access layer (DAL). Utilizing distinct layers allows us to make significant changes to one layer without affecting the other layer.

The Zone Strategy

Maintaing separate business logic and data access layers is a valuable strategy and works well with Nexus commands. An alternative strategy is to create both business-logic commands and data-access commands which can be mixed in a chain as needed.

Under the Zone strategy, the context serves one of the important roles of an interface: it reduces coupling. You can change the implementation of one of the commands without changing the other commands in the chain. A data-access command can place values in the context and a business-logic command can utilize the values. Likewise, a business-logic command can synthesize values to be used by a data-access command later on down the chain.

A conventional data-access interface might specify methods for create, retrieve, update, and delete. Under the Zone strategy, common operations can be represented as base commands. A BaseSave command can provide default behavior for inserts and updates. Rather than extend BaseSave through inheritance, we can "extend" the base element and specify different settings.

Under the Zone strategy, the business-logic and data-access operations are still distinct, but they are not separated into different layers. They live in different "zones" of a unified command layer. The Zone strategy maintains separation of concerns, but without creating and maintaining separate BLL and DAL hierarchies.

As mentioned, a conventional BLL/DAL architecture works just fine with Nexus. The Zone strategy is simply an alternative to consider.

Actions and Commands: Kissing Cousins

In the popular Struts tradition, Nexus 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". Nexus applications describe themselves in terms of business-centric "Commands", independent of the web platform.

In a Nexus application, the presentation layers (be they ASP.NET, Struts Classic, JavaServer Faces, et al.) 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 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 a Nexus application, the inputs and outputs are explicit.

XML Examples: Show don't tell

Command Elements

Here's some example Command elements:

<!-- Obtain a single entry -->
   <object id="entry" parent="BaseEntry">
   <property name="ID">
   <value>entry</value>
   </property>
   <property name="RequiredID">
   <value>entry_key</value>
   </property>
   <property name="OutputIDs">
   <list>
   <value>last_name</value>
   <value>first_name</value>
   <value>extension</value>
   <value>user_name</value>
   <value>hired</value>
   <value>hours</value>
   <value>editor</value>
   </list>
   </property>
   </object>
   
   
   <!-- Obtain a list of objects that can be filtered by various fields -->
   <object id="entry_list" parent="BaseList">
   <property name="ID">
   <value>entry_list</value>
   </property>
   <property name="QueryID">
   <value>entry</value>
   </property>
   <property name="InputIDs">
   <list>
   <value>last_name</value>
   <value>first_name</value>
   <value>extension</value>
   <value>user_name</value>
   <value>hired</value>
   <value>hours</value>
   <value>editor</value>
   <value>entry_key</value>
   </list>
   </property>
   <property name="OutputID">
   <value>entry_list</value>
   </property>
   </object>
   
   <!-- A list of values for a drop-down list -->
   <object id="last_name_list" parent="BaseFilterList">
   <property name="ID">
   <value>last_name_list</value>
   </property>
   <property name="OutputID">
   <value>last_name_list</value>
   </property>
   </object>
   
   <!-- A chain of commands -->
   <object id="directory_view" parent="BaseChain">
   <property name="ID">
   <value>directory_view</value>
   </property>
   <property name="AddCommands">
   <list>
   <ref object="last_name_list" />
   <ref object="first_name_list" />
   <ref object="extension_list" />
   <ref object="user_name_list" />
   <ref object="hired_list" />
   <ref object="hours_list" />
   </list>
   </property>
   </object>
   
   <!-- Page helpers -->
   <object id="directory_find_helper" parent="BaseHelper" singleton="false">
   <property name="Command">
   <ref object="directory_view" />
   </property>
   </object>
   
   <object id="directory_list_helper" parent="BaseHelper" singleton="false">
   <property name="Command">
   <ref object="entry_list" />
   </property>
   </object>
   
   <!-- (optional) parent elements -->
   <object id="BaseChain" type="Nexus.Core.RequestChain, Nexus.Core"/>
   <object id="BaseEntry" type="PhoneBook.Core.Commands.BaseEntry, PhoneBook.Core"/>
   <object id="BaseFilterList" type="PhoneBook.Core.Commands.BaseFilterList, PhoneBook.Core"/>
   <object id="BaseList" type="PhoneBook.Core.Commands.BaseList, PhoneBook.Core"/>

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

FieldTable Elements

More detail about the fields can be provided in the "FieldTable". The FieldTable provides background information on whatever fields an application uses. The target data type, validation messages, labels, and 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. Other types, like numbers and dates must be specified. Here's a simple FieldTable that specifies a Date field:

<!-- FieldTable -->
   
   <!-- Strict is not enabled, so we only need to list fields that are
   (1) need special formatting (via a Processor),
   (2) represent a list with fields that need a Processor (fields listed because of (1))).
   The default processing will apply .ToString() to any unregistered fields,
   and pass through any unregistered lists verbatim
   (which is cool if all the fields on the list are strings that don't need formatting).
   -->
   
   <object id="FieldTable" type="Nexus.Core.Tables.FieldTable">
   <property name="AddFieldContexts">
   <list>
   <ref object="extension"/>
   <ref object="hired"/>
   <ref object="_entry_list"/>
   <ref object="_extension_list"/>
   <ref object="_hired_list"/>
   </list>
   </property>
   <property name="AddProcessors">
   <list>
   <ref object="TelephoneProcessor"/>
   <ref object="DateTimeProcessor"/>
   <ref object="EntryListProcessor"/>
   <ref object="ExtensionListProcessor"/>
   <ref object="HiredListProcessor"/>
   </list>
   </property>
   </object>
   
   <object id="extension" parent="BaseFieldContext">
   <property name="ID"><value>extension</value></property>
   <property name="Processor"><ref object="TelephoneProcessor"/></property>
   </object>
   
   <object id="hired" parent="BaseFieldContext">
   <property name="ID"><value>hired</value></property>
   <property name="Processor"><ref object="DateTimeProcessor"/></property>
   </object>
   
   
   <!-- We need to "hash" the name with "_" to avoid conflict with the filter Command -->
   <object id="_entry_list" parent="BaseFieldContext">
   <property name="ID"><value>entry_list</value></property>
   <property name="Processor"><ref object="EntryListProcessor"/></property>
   </object>
   
   <object id="_extension_list" parent="BaseFieldContext">
   <property name="ID"><value>extension_list</value></property>
   <property name="Processor"><ref object="ExtensionListProcessor"/></property>
   </object>
   
   <object id="_hired_list" parent="BaseFieldContext">
   <property name="ID"><value>hired_list</value></property>
   <property name="Processor"><ref object="HiredListProcessor"/></property>
   </object>
   
   <!-- "d" is .NET for "short date" -->
   <object id="DateTimeProcessor" type="Nexus.Core.Validators.DateTimeProcessor">
   <property name="ID"><value>DateTimeProcessor</value></property>
   <property name="DataFormat"><value>d</value></property>
   </object>
   
   <object id="TelephoneProcessor" type="PhoneBook.Core.TelephoneProcessor">
   <property name="ID"><value>TelephoneProcessor</value></property>
   </object>
   
   <object id="EntryListProcessor" type="PhoneBook.Core.AppEntryListProcessor">
   <property name="ID"><value>EntryListProcessor</value></property>
   </object>
   
   <object id="ExtensionListProcessor" parent="BaseKeyValueProcessor">
   <property name="ID"><value>ExtensionListProcessor</value></property>
   <property name="Key"><value>extension</value></property>
   </object>
   
   <object id="HiredListProcessor" parent="BaseKeyValueProcessor">
   <property name="ID"><value>HiredListProcessor</value></property>
   <property name="Key"><value>hired</value></property>
   </object>

If other fields were added that needed to be processed in the same way, they could share the same Processor. Or, if a similar field were added that need to be processed differently, say with an alternate date format, another DateTimeProcessor could be configured under a different name.

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

Alerts, Errors, and Faults (Oh my!)

The error messaging system 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 messages need to be localized, an alternate FieldTable implementation can be used which reads language tokens from a standard message resource. Even if your application is not being globalized, you might find it more convenient to place text messages in a resource rather than in the FieldTable configuration.

ViewHelper

On the presentation layer, a Nexus application can use simple ViewHelper classes to integrate with the [MVC Framework].

private void Find_Load ()
   {
   IViewHelper h = ViewHelper;
   h.ExecuteBind (pnlFind.Controls);
   bool ok = (h.IsNominal);
   if (!ok) Page_Error = h;
   }
   
   private void Filter_Changed (object sender, EventArgs e)
   {
   DropDownList list = sender as DropDownList;
   FindHelper.Criteria [list.ID] = list.SelectedValue;
   List_Load (FindHelper);
   }
   
   private void List_Load (IViewHelper helper)
   {
   helper.Execute ();
   bool ok = helper.IsNominal;
   if (!ok) Page_Error = helper;
   else
   {
   IList result = helper.Outcome;
   repList.DataSource = result;
   repList.DataBind ();
   }
   }

As mentioned, the base ViewHelper uses the FieldTable to automatically validate input and format output.

Nexus.Extra: But wait there's more

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

For example, the Spring.RequestCatalog class is Spring-Aware. If you use the Spring RequestCatalog, you can create RequestCommands 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.

AppEntry: The last mile

AppEntry is an optional class that you can extend with specific presentation properties in each application's namespace. An AppEntry combines the best features of a IDictionary and a Property object. You can expose the attributes your pages display as Properties but store the values in an IDictionary. The Properties work well with page controls. The IDictionary works well with persistence utilities, loops, and collections. Usually, a Processor is used to automatically convert and format output from the business layer (e.g. database) to an AppEntry or list of AppEntries.

Since it is meant to be used with presentation layer controls, as a rule, an AppEntry will store only String properties.

OverDrive applications use a "coarsely-grained" AppEntry. For smaller applications, with say, up to a hundred display properties, we might use a single AppEntry. If a page doesn't use a property, then it's null, no harm done. Larger applications might want to use AppEntries that align with major workflows.

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

"Transfer Object"

"Use a Transfer Object to encapsulate the business data. A single method call is used to send and retrieve the Transfer Object. When the client requests the [business object] for the business data, the [business object] can construct the Transfer Object, populate it with its attribute values, and pass it by value to the client."

Core J2EE Pattern Catalog

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

Planned Enhancements: The cool stuff is yet to come

AppContext Generation Whatever properties we need in an AppContext can be gleaned from the FieldTable, and so we could generate the code for a standard AppContext from the FieldTable.
DataForm Generation If you match up the list of input fields a Command uses with the FieldTable, that lists the Control Type, Label, and Hint for each field, we should be able to generate a simple data-entry form for a Command.
Command Cache Products like iBATIS can cache the result of a Statement, and then refresh the Cache when a timer runs out or a certain Statement is run. We should be able to do the same thing for Commands. If a Cache is added to the Catalog for a Command, and the same input is provided, then we should be able to cache and return the same output (after formatting!), just like iBATIS does.

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