Two architectural demands for the Silverlight HVP, which are common to many larger applications, came together this week and crystallized into a design that looks to solve a couple other, unanticipated requirements. Specifically, we knew that (1) we wanted to have configuration files and that (2) we wanted each module to be created independently of all the other modules. After some discussion, thrashing, input from some great folks at CodeMash, and further review, the design that has emerged allows us to implement two other less thought-through requirements: (3) the ability to restore the state of the application to an earlier condition and (4) the ability to save the state of the application when it is shut down.
The New Design For Configuration
All of this is much easier to understand by diving into the design that has emerged. While the UI has not been determined, the core modules have been identified, and it is easier to conceptualize them with images; even if they don’t necessarily reflect how the modules will be presented to the user.
Function Vs. UI
Below are two sketches of potential User Interfaces for the same four objects; neither of these represents a considered UI design, but rather are here to make concrete the underlying structure that would support such disparate designs. (Click on images to enlarge)
In the standard mock-up (which may well represent the standard UI), the HVPItemsControl contains a list of all the items (videos, animations, etc.) that will be viewed in this HVPSet.
The Current Item selected (or defaulted to) determines the topics to be listed in the HVPLocationsControl and the hyperlinks listed in the HVPLinksControl.
Selecting a location causes the HVPPlayer Module to jump to the corresponding location. Selecting a new Item causes the HVPLocationsControl and the HVPLinks Module to fetch their new data
Selecting a link causes a new HVPSet to be created, potentially with its own Items, each of which has its own locations and links.
Distinguishing the Module from its UI
Before discussing configuration, event and state management, let’s look at a quick mock-up of another UI for these four primary modules (excluding the speculated Cloud Module)
The goal of creating this second mock-up, again, is to help wrap our heads around the difference between the functionality of the various modules, and their appearance (UI).
It should be quite easy to create a UI like this just with with templates, though no one is suggesting that this amalgam of different approaches is a serious design.
(Though, I must admit, I like the links appearing in bubbles, perhaps with a soft popping sound as they animate into existence from a point, expanding outwards).
The requirement is that each module can be created in any order, and that no module is required, nor does any module know about any other. There are a number of ways to architect for this; though the problem is both constrained and (therefore?) simplified by the decision to create a composite application using MEF.
My strong inclination is to treat design of anything you are not about to code as a form of optimization: best left until you know what is needed. I’m a firm believer in Emerging Design, which, for me comes down to the recognition that you cannot know, in advance, what your architecture should be, because both the requirements and the design will evolve in response to the very act of building the application.
You can, and should, however, distinguish between a flexible architecture and a brittle one. One great way to do so is to throw ideas at the architecture and see if it has to change significantly every time you add a feature or a new way of approaching the problem. If the architecture nimbly adjusts and, better, expands on the requirement to reveal new possibilities, you are in great shape. (If it creaks and threatens to break with every new idea, then it is time to rethink!)
We have made the following decisions, to be implemented in the next three weeks:
- MEF will be used to compose the application
- An Event Manager (HVPEvents) will be created, and each module will register with HVPEvents when created by MEF
- Events will signal a change in state of the current HVPSet [footnote 1]
- Modules will check the state object to determine what their current configuration should be
- Configuration will be created first as objects, then persisted with Xaml [footnote 2]
|Markup Model Equals Object Model – Rob Relyea|
The rest of this posting will focus on two key ideas:
- Configuration and state persistence will be captured in objects and persisted with Xaml
- Modules announce only that their state has changed; interested modules then chek the current state.
Configuration, Objects, Xaml and Persistance
The state of a module is represented by a corresponding state object (e.g., HVPLocationState). The state of the application at any given moment is held by an ApplicationState object and represented as an HVPSet. The ApplicationState is, in turn, composed of the module state objects. Thus, the state of the application illustrated above would be represented by an HVPSet object that contains an HVPLocationState object, an HVPItemsState object and an HVPLinksState object. Note that there is no state object for the player as the player’s state is determined by the other objects (at least at this moment).
[Note, the state objects are not implemented in the current release, and are really mental placeholders for a final design. The goal, however, is to have objects that correspond to the information stored in the Xaml configuration file]
The ApplicationState object will initially only exist in memory. In time, it will be persisted either locally (local storage) or on a server, and accessed via a URL.
I’ll walk through this below, but note that this design will be implemented in stages, roughly:
- First stage: we only know about configuration state, and that is hard-wired
- Stage 2: we know about configuration state by reading a Xaml file and creating the objects
- Stage 3: we know about configuration state by reading Xaml files (plural) identified by the Modules and assembling the configuration state
- Stage 4: we can return to a previous state by storing a collection of HVPSet objects in memory, each of which consists of state objects for each module
- Stage 5: we can persist state by writing the members of all the HVPSet objects to Xaml files.
- Stage 6: The state of the application can be persisted by a service which returns a URL to that state. The user can then store that URL (to resume later) or send it to someone else (“Check out this very cool video and note the links to our site!”) that will allow another user to restore to the exact state that was saved, including the location in the video, the other items enqueued, the links displayed, etc.
Configuring Each Module
When a module is added to the application by MEF it will register itself with the HVPEvents class. [footnote 2]
The module will ask the HVPState for its own state object (e.g., the HVPLocation module will ask for the HVPLocationState object). If that object is not null, the module will use the state object to configure itself.
When an object wishes to raise an event, it will send a message to HVPEvents which will, in turn, raise the appropriate event. The event will signal modules that subscribe to that event that they need to obtain their State object and their use of the state object will be identical to their actions in the initial configuration.
Let’s walk through a couple scenarios:
Bootstrapping when the user selects a video or set of videos
In this scenario the application has not started on the user’s machine, and the user makes a gesture (e.g., clicks on a link) to “watch” a video or an animation, or more likely an entire “experience.” In any case, the URL resolves to an HVPSet which is passed to the application on start up.
As the Application bootstraps it creates the ApplicationState (a Singleton). The ApplicationState is given the Uri of the selected HVPSet, which returns a Xaml file. The ApplicationState asks the ConfigurationParser to convert the Xaml into an HVPSet. In version 1, the HVPSet that is created has the Uri of the HVPItemsState.xaml file.
When MEF adds the HVPItemsModule, the HVPItemsModule requests the HVPItemsState object from ApplicationState. The AppliationState requests the HVPItemsState from the HVPState, which in turn asks the ConfigurationParser to read the HVPItemsState.xaml file and to return an HVPItemsState object which it then holds a reference to. The HVPState object also returns a copy of the HVPItemsState to the HVPItemsModule(by way of the ApplicationState object.) The HVPItemsModule then sends a message to HVPEvents which in turn raises the ApplicationStateChanged event.
The HVPLocationModule / HVPLocationState objects and the HVPLinksModule / HVPLinksState objects are created in much the same way.
When the HVPViewer is initialized, it obtains the current HVPItem from the ApplicationState which it uses to determine what type of HVPFrame to create. The HVPFrame in turn creates the appropriate HVPPlayer which, in its turn, obtains the item using the same mechanisms. The HVPViewer registers to obtain StateChange notifications so that if a different HVPItem is requested the correct Frame and Player can be created.
Handling User Generated Events
While an item is displaying the user can take any of the following actions:
- Click on an item in the ItemModule
- Click on a topic in the LocationModule
- Click on a link in the LinksModule
- Stop the application
- Request a snapshot of the current state of the application
- Request a new set from the server (by whatever gesture the user began the application)
Clicking on an Item causes the HVPItemsModule to send a message to HVPEvents which raises the ApplicationStateChanged, which in turn causes the HVPLocation module and the HVPLinks module to re-set their state based on the current item.
Clicking on a topic causes the LocationsModule to send a message to HVPEvents which raises the ApplicationStateChanged, which in turn causes the the HVPLinks module to re-set its state and the HVPPlayer to seek to the requested location.
Clicking on a link causes the HVPLinksModule to send a message to HVPEvents which raises the ApplicationStateChanged, which in turn causes all the modules to set their state based on the new HVPSet. (Each link is tied to an HVPSet with its own set of Items).
In a version after V1, Stopping or closing the application or requesting a snapshot will (probably) cause a dialog box to open to ask the user if state should be saved, which is accomplished by having the ApplicationState save its current HVPSet either locally or on a server, with a Uri returned which is then saved locally.
In the current design the only event is the ApplicationStateChangedevent. For now, therefore, we will collapse HVPEvents into the ApplicationState object, and modules will not need to register, they will obtain the ApplicationState Instance and request that it fire the ApplicationStateChanged event.
Marker Reached, TimeObject Created, TimeReached
Anof this design designated a number of other events that are now subsumed under the ApplicationStateChanged event, including the events needed to signal the HVPPlayer that the HVPLinks module has created a WatchedTime collection (for elapsed times at which it needs to be notified) and the ability for the HVPPlayer to notify any interested modules that a WatchedTime has arrived or that a Marker has been reached.
All of these are now handled as described above; that is, e.g., when the HVPPlayer reaches a Marker or a WatchedTime it updates the ApplicationState which in turn fires off an ApplicationStateChanged event, and the interested modules reset their own state accordingly (e.g., the HVPLinks module might add a link to its list).