'Prism' RI first drop
Today is a historic occasion for the "Prism" team as our first drop of our Reference Implementation (RI) hits codeplex. In this post I will describe to you what the RI is, and what you can expect to see when you download it.
Disclaimer: This will evolve (and change) significantly. At this stage of the game you should be looking at this to get a sense of where we are heading and to see the different patterns we are applying. What is in this drop is not fully baked (or even half-baked :) ). This is not a CTP, this is not a BETA, it's our team being as visible as possible into our development efforts.
<WARNING>Long post follows</WARNING>
Why a Reference Implementation?
Before we jump into the details, let's take a step back and talk about what an RI is, and why we are creating one. An Reference Implementation is essentially a sample application that is designed to illustrate a standard (design patterns, technical specifications) in order to help others implement their own versions of the standard. The RI is not a real production application however it needs to illustrate real-world scenarios, in order to be relevant. Within patterns & practices these RIs are super-important in that they are one of the major stores from which we mine our guidance.
In order to achieve the "real-worldness" of our application, we work with our customers and partners. They tell us "Yes, this is something we do (or could see doing)" or "No, you are out of your mind" :) We're certainly not experts in every business domain, so we rely on the ecosystem heavily.
As we build out the RI, we see different recurring patterns emerge around the technical challenges that the RI is illustrating. As as we see these patterns we continually refactor in order to address them in a cleaner fashion. As part of this refactoring we apply key software principles such as Separation of Concerns (SOC), Single Responsibility Principle (SRP). We also look to implement known design patterns as part of the implementation.
What about the framework?
Over time as we refactor more and more, a framework / set of libraries begins to also emerge. We're not setting out with any hard and fast expectations about the framework, though we know it is likely one will exist. If we've done our work right, and stuck to the YAGNI principle, then this framework will not be the answer to world hunger, but rather it will be an answer for the RI and for applications with similar patterns. That does not mean that it won't be applicable for a wide range of applications, as if the scenarios in the application are common, then that may very well be the case.
It's up to you
I can't reiterate this point enough. We're not guaranteeing that the patterns / framework used in the RI will be right for your scenarios. Only you can make that decision. What we can say is that we are confident that the techniques / patterns we're using are a good fit for the scenarios in the RI.
Not the only fit!
This is just one of the many different ways to skin a cat. It is a way, not the only way. For your scenarios it may even be the wrong way.
In the case of "Prism" we're building a WPF application that is a Composite client. That means in addition to looking at the essential aspects of a Composite, we want to look at what is special about building them in WPF. At a high level we want to illustrate the following concepts:
- Shell - The application should contain a place to host different UI components. The shell itself should be lightweight and not "know" about what contents it will contain. As it is essentially dumb, the shell needs to expose a way for itself to be populated by others.
- Layout - The shell should contain place holders for content that will be displayed in the shell.
- Modules - The application should not be monolithic rather it should be developed as a set of separately tested and deployed modules. These modules can be developed and maintained by separate teams.
- Modules need to have a common place for initialization code.
- Loading - Modules can be statically or dynamically loaded.
- Static modules are modules that are directly referenced and the shell is hard coded to load them. The shell kicks off the load, but does not have any knowledge into what it is loading.
- Dynamic modules are modules that are discovered either through sweeping a folder (Eclipse style) or through configuration.
- On Demand. In future releases we will be looking at how to specify that a module loads on demand.
- Styles - Modules should be able to be individually styled by a designer using Expression blend. Global styles should also be able to be specified
- Views - Modules should contain different content which is proffered up to the shell.
- Human Designers should own the UI (either that or a developer playing the role of the designer). This means not making hard and fast rules about how things look, including menus and toolbars.
- WPF Databinding should be levered wherever possible.
- WPF Commands should be used for binding UI actions.
- The UI should illustrate making good use of data visualization
- The UI should illustrate floating / overlaid content as well as dynamic content that appears and disappears
- The UI should be testable and implement appropriate design patterns for SOC. The likely candidates we've been looking at our Model View Presenter and Presentation Model.
- Communication - Different components within the application should communicate with one another whether they reside in the same or different modules. This needs to happen without the modules requiring hard dependencies on one another. Communication should be thread-safe.
- Services - The application should expose a set of services that can be accessed by the modules in a loosely coupled manner.
As we develop the app we have a few over-arching goals or architectural qualities that we are thinking about (this is a partial list of special qualities in addition to the usual suspects). Some of these are motherhood and apple pie, so we are developing detailed acceptance criteria (though not an exhaustive list) of specific things we want to test to see if we are meeting the mark.
- Simplicity - Simple, but not too simple. For the simple cases, getting there should be simpler than for the complex cases. This is basically applying the 80/20 rule otherwise known as the Pareto Principle. It means that 80% of the cases can be handled by addressing 20% of the causes. The remaining 20% of the cases can be addressed with other means. Simple also implies that we are limiting the number of abstractions as too many certainly leads to more complexity.
- Usability - In addition to making the concepts easier to swallow, we're thinking about how you apply those concepts hence the features. This means we are making a tradeoff between having more features in order to have less features that are more usable.
- Subsetability (no I didn't make it up) - Ability to pick and choose capabilities turning them on or off or as I mentioned not requiring you to take the whole kitchen sink. At high level we think this means if all you want is the composite UI capabilities but have not bought into dynamic module loading (or vice versa) then you can choose. At a more detailed level it means if all you want to do is pull in our EventBroker (we will probably have one) then you can.
- Composition - This is the whole Object Composition vs Inheritance topic. Without getting in to all the details, it means that you will have a lot more flexibility in how your application wires itself up into the framework i.e. via interfaces vs requiring you to inherit from a whole set of core constructs. You've heard of the old Hollywood principle of "Don't call us, we'll call you". Well this is more of a "I'll call you and give you directions on how you can call me".
- Letting you use the DI / container framework of choice. This means using Unity, Castle Windsor, StructureMap, Spring.NET, or even a Hashtable :)
- Allowing you to leverage existing utilities. This means using Enterprise Library's logger, Log4Net or your own logger if you so desire.
- Compatibility - Not requiring you to start from scratch. Essentially this means being able to take the concepts and apply them to an existing application. The focus here is around WPF not WinForms (though we will look at WinForms via Crossbow). As an example we have acceptance criteria that defines taking an existing WPF application and adding UI composition to it without having to completely rework it.
For the "Prism" RI, we decided to do a stock trader application. There are several reasons for why we chose this. For one thing, trader apps make good use of data visualization which is a core aspect of why one would use WPF. Also, several customers we spoke to are building trader or trader-like apps. Third, we presented the idea to our advisory board and they liked it :). The stock trader app we are envisioning has several core components. You can see a screenshot below which illustrates what you will see in the drop.
Contains a list of funds that you have invested in along with relevant information about each position. As you select a position, the Trend Chart changes to reflect the current position. Items within the position may have associated news which can be access by pressing the "News" button.
A list of funds that you are watching. Notice that the list currently appears when you hover on the "Watch List" tab. There's also a pushpin for making the list more permanent. "From the toolbar, you will notice an "Add to Watchlist" button which will in the future allow you to enter in a ticker symbol and add it.
Displays a pie chart with a breakdown of your portfolio.
Displays news articles. News is populated by various different sources and related to various actions. Currently it is only populated based on clicking the "News" button. In the future it will also be populated based on the trend chart.
Displays trend data for the selected symbol. In the future you will be able to hover over points in the trend line and see a popup of related news links for that fund on that date. You will then be able to click one of the links and populate the news tab with that article.
Buy / Sell
The Buy / Sell screen will allow you to create a batch of buy / sell transactions. This functionality does not yet exist, but it will in the future. We think this screen is critical for exploring more complex scenarios.
I'll be following up with much more detail in future posts, as will the rest of my team. For now let's just paint a high-level picture of each of the players.
We ship two solutions, one for the RI + Unit tests, and the second for the Acceptance tests. I'm not going to cover the acceptance tests for now, but if you are interested go check them out. You'll need Thoughtworks' White to be installed in order to run the tests.
When you open the solution, you may be terrified when you see this....
Yes, we have 15 projects already! These are not "frameworky" type modules, rather they are the different modules maintained by the teams, as well as the unit tests. To get a simpler view, go and collapse all the folders until you see this.
StockTraderRI is the main project that you want to execute. As to the rest of the folders
- Modules - Contains the various application modules which are maintained by the various teams
- Common - Contains reusable Framework / Library code.
- Unit Tests - Contains VSTS unit tests. We have about 60 so far, and the number grows. We are building out the app in a Test-First design style, so we're writing all of our tests first and stubbing out / mocking implementation.
As to the projects themselves, here is a break down (not of the test projects) along with interesting tidbits.
- Prism.csproj- Contains the core services and utility classes that are reusable. Currently this only contains the MetaDataInfo class, and the RegionManager service and it's related classes. We expect this will grow.
- Prism.Interfaces.csproj - Contains interfaces for common services and types.
- StockTraderRI.csproj - Executable which launches the application. Currently it contains only an App.xaml, Shell.xaml and a Bootstrapper.
- The Shell is simply a WPF Window, and does not require you to inherit from any special base class.
- You'll notice within the shell we have Regions. Regions are placeholders for content. You'll see we're using an attached property (prism:RegionManager.Region) in order to automatically register these regions. Once a region is registered, it can be accessed by the RegionManagerService.
- NewsRegion is the region where the news is displayed. It is a special kind of region where each item added appears in a separate tab.
- The Bootstrapper does a few things:
- Initializes the container (can be replaced to use any container but currently uses Unity)
- Registers some core services
- You'll see a RegionManagerService. This service is what is accessed by various modules to populate the shell. When a module has content it needs in the shell, it grabs the region manager.
- Shows the shell
- Intializes the modules.
- You'll notice here that we are doing static module loading, meaning the shell actually does have hard-references to the modules, though they don't reference it. The shell does not know anything about the modules other than in interface it can call to initialize them., We're doing this deliberately because in some smaller apps, it may not be a requirement for dynamic loading.
- Each module implements an Initialize() method. In this method is where services / views / presenters etc are registered with the container, it is also where views will be shown and added to the Shell.
- The Shell is simply a WPF Window, and does not require you to inherit from any special base class.
- StockTraderRI.Infrastructure - Contains shared domain and presentation models that are used across modules.
- StockTraderRI.PositionModule - Displays the different positions including the portfolio breakdown.
- Contains an AccountPositionService which retrieves the list of positions to be displayed.
- Contains a PositionSummaryPresentationModel which is an observable collection of positions.
- Adds the TrendLine and PositionGrid. Although the TrendLineView actually exists in a separate module, that module does not inject it. Instead, the PositionModule asks the container to give it an instance of ITrendLineViewPresenter, and has the view show it. Because the PositionsModule gets that pointer, it can use that interface to notify as a new position gets selected. In CAB this would have traditionally been handled through an EventBroker, as the Trend line would have been injected. Here we are being more explicit, with the benefit of being able to directly call between the modules without needing EventBroker. We are not ruling out that we will have an EventBroker type functionality as we believe it is needed, though not in this instance.
- RI.WatchListModule - Manages the watch list functionality.
- Contains a WatchListView and WatchListView presenter which is shown during the initialization. You'll notice that the Presenter drives all the interaction, so that in the WatchListModuleInitializer we are creating a WatchListPresenter through the container. The Presenter then accepts a WatchListView which is passed to it's constructor. The DI container will find handle injecting this.
- Also contains the AddWatchControl. This does not have a Presenter as it does everything through databinding.
- StockTraderRI.NewsModule - Manages the aggregation and display of news. We imagine a news team would handle locating news from various services that relates to the funds being displayed.
- Contains a NewsFeedService which is responsible for retrieving news for a particular ticker symbol. Returns NewsArticle instances that encapsualate the news. Currently it only supports one article per symbol.
- Contains a NewsService which is responsible for displaying the news for a particular symbol. The NewsService works with the RegionManager to see if the article already exists. If not, it creates an instance of the INewViewPresenter. The presenter itself actually populates itself to the region by grabbing hold of the RegionManager itself.
- One important aspect of this is that when the News article is shown there is additional information that needs to be provided to the tab, such as an icon. Instead of having the NewsService have to manage this information, we've encapsulated it within the view itself. The ShowNews method on the presenter sets Metadata (MetadataInfo) on itself. The tab region then pulls this Metadata to set it's icon and such.
- StockTraderRI.MarketModule - Manages retrieving market data for each symbol and displaying the trend chart.
- Contains MarketFeed and MarketHistory services, the MarketFeed service contains the current information, and the MarketHistory contains historical information with fund fluctuations.
- TrendLineView uses a hybrid of Model View Presenter and Presentation model for presenting the data. The Presenter sets up databinding. The Presentation model contains all the data displayed in the trend line and is bound directly to the UI. The TrendLineView / Presenter are registered with the container on initialization but not displayed.
OK, I'll stop now as I can see myself going on for hours and hours. As I mentioned above this is just the beginning, and there's much more to come in the future. This includes documentation around the RI itself and the patterns in play.
Go get the bits here!
We WELCOME your feedback ;-) As my team-mate Michael always says, "Have Fun!"