Overview of the Model Layer
The Mvpc design pattern is based on four layers:
- Model
- View
- Presenter
- Command
This article is part of a series covering each layer of the Mvpc design pattern in detail. This post covers the Model layer.
The model layer itself takes on four core responsibilities that are exclusive to itself:
- The representation of data as models.
- The storage and retrieval of data into models via repositories.
- The conversion of models to and from different shapes.
- The mark-up of metadata against models to provide hints to users of a model.
The representation of data as models
When designing Mvpc we took the decision to define a model in as broad terms as possible. We wanted to make sure anything a programmer would generally call data could be treated as a model. For this reason we decided that a model would not be based on any existing framework or data modelling tool, and would not require the sub classing of a specific base type or implementation of an specific interface. Instead we decided that a model for the framework would be a property bag; that is to say a collection of name and value pairs, where the type of the value is usually known. In practice this means developers are able to see simple code classes as models, as well as more complex data structures.
Using a property bag as a model gives us a lot of flexibility; and means we are compatabile with models generated by almost any existing or future data abstraction or framework such as datasets, the entity framework, POCO, NHibinate,, IDataReader, IDictionary<,>, properties defined as simple code classes and structs, and anything else you can imagine.
Using this technique means that the Mvpc pattern and related libraries do not lock a developer into a particular choice of data abstraction or database engine; but allows developers to continue to use the data abstraction layers they are already used to and familiar with.
The storage and retrieval of data into models via repositories
Once we had decided upon a representation of a model, we needed a way to keep the developers choice of data abstraction separate to the model and the use of a model at point of consumption in a command, view, or code block. We decided to do that by abstracting the retrieval and storage of data through a simple, well defined repository API (covered in this section), and by allowing a model to change shape to better fit the context of its use (covered in the next section).
The repository is completely responsible for the creation, storage, and retrieval of models. It is responsible work directly with a data store, and to return data in the shape most applicable for the executing code to use it.
Unlike for the model itself we do insist on a simple base interface for a repository: IReposiitoryBase.
The IRepositoryBase interface exposes type unsafe methods for the four basic data management operations:
- Find
- Create
- Update
- Delete
In addition it supports two key query methods for working with ad-hoc queries:
- FindAll
- FindFirstOrDefault
Then based on over a decades experience of working with data abstraction models and data processing applications we also decided to build into the base two specific “predefined” query methods that would serve a similar purpose to server side views in an SQL database. These methods are:
- GetDisplayList
- GetSelectionList
Both of these methods accept an optional query name as a parameter, so the number of queries then can support is unlimited; and importantly the return type is not expected to be the same type returned by the data operations and ad-hoc query methods, and will usually contain data that is not directly stored in the model. For example a repository for a Sales Order will return or take a SalesOrder model or IEnumerable<SalesOrder> for the data operations but depending on the query name you pass in could return a list of sales order details including a customer names and addresses that do not make up part of the model; or a summed result of total sales broken down by month and year. For this reason the query methods are both defined as simply returning a System.Collections.IEnumerable to support any collection type at all.
As many of you will know there are pros and cons to making a repository completely type-safe, or independent of type. We won’t go into them in detail here, but it can generally be summarised that type safe repositories as easy for an end user to work with, and for developers to specialise, but type unsafe repositories can make code more reusable and cope with types that didn’t exist at compile time.
In the end we decided that neither approach alone met our design goals completely so we decided to provide both APIs and let the methods using the repository use the one it found most useful.
Therefore we provide an interface IRepositoryBase that exposes only type unsafe versions of methods. These are easily recognisable in the Mvpc libraries as their names all end in “Untyped” e.g.:
object CreateUntyped() bool UpdateUntyped(object)
We then provide an interface IRepository<T> that exposes only type safe versions of the methods type safe to the generic parameter T. These are generally the more convienent methods to use, so we’ve left these with the simple names with no suffix, e.g.:
T Create() bool Update(T)
Its worth just pointing out at this point that as the GetDisplayList() and GetSelectionList() methods do not return items match the repositories model type, they are included in IRepositoryBase and do not have explicitly named Untyped() versions.
The final piece in the dual API design was to make it possible for developer consuming a repository to choose either API, but for the developer creating the repository implement only a single API.
To achieve this we introduced an abstract base class RepositoryBase that by default linked the “Untyped” methods to calls to the type safe methods so programmers could write type safe repositories and have the unsafe APIs automatically provided.
While that approach worked well for people writing a specific repository, it explicitly links a repository implementation to a realised class at compile time as not all platforms can support dynamic code execution or the System.Reflection.Emit namespace.
We still didn’t feel this was good enough, and so we provided a set of utility methods that allowed a repository designer to write a repository that only found out its model type at run time, but could still handle the type safe calls by exposing a proxy class to allow those calls to be made.
Because of this we were then able to bundle a number of commonly used options for repositories into the libraries creating a powerful starting point for applications using the libraries. To avoid bloat and unwanted references to 3rd party libraries, we made each available as a separate package on Nuget so you only need to reference the types you want to use in your application or module.
For each standard repository types we provided two sets of classes:
- A type safe generic base class that provides full implementation of the API and allows extension of the API by a developer via subclassing.
- A fully dynamic repository that will adapt itself to any model type provided at run time.
The first option is self explanatory and works through normal sub classing and overriding of methods. The second option however allows us to do particularly interesting things like: rapid prototying repositories for any Entity Framework .edmx file; switching or adding optional support for a new database engine or web service without having to redevelop or even recompile any of the existing code; or providing Json wrappers around any other repository type.
Here is a list of the supported data abstractions that have full support for repositories in the reference Mvpc libraries:
- Entity Framwork 5
- Entity Framework 4
- ADO.NET (System.Data)
- POCO
- Code Classes via System.Reflection
- XML files
- JSON Web Services
- JSON Database (Mvpc.JsonDatabase – a local database that can work on any device and uses json to store its data)
- JsonRepository webservice (a web service that will wrap any other repository and provide secure access to it via a Json based web service).
The JsonRepository webservice is an important option for us to include as not all data abstraction frameworks work on all platforms. For example ADO.NET or the Microsoft Entity Framework can’t be accessed directly from Windows Store applications or Windows Phones. Rather than saying “you can’t support these platforms if you use EF5 for data abstraction” we instead provide a built in wrapper that means you can. And because of the simple design the same wrapper can be used if you build a repository that uses NHibinate or other simpler tool.
The final note to make about the repository API is that when we designed it we always wanted to support both synchronous and asynchronous operation. Therefore on the platforms that support asynchronous events we have provided an Async version of each of the typed and untyped repository APIs via extension methods. e.g.:
T CreateAsync() bool UpdateAsync(T) object CreateUntypedAsync() bool UpdateUntypedAsync(object)
This means if you are building a custom GUI component to enhance a platform you can use the Async() apis to keep your user interface responsive ,while carrying out repository access that might be accessing remote servers or other potentially slow resources.
As the Async API are provided via extension methods repository designers do not need to implement their own async versions of the repository and can usually ignore the fact their repository will every be accessed asynchronously. This keeps back with our design principle that a repository should be designed as if there was a single API, but should allow use by the right API for the task at hand.
The Async API is completely compatible with the C# 5 async and await keywords, so they couldn’t be easier to use. As these keywords are still new to a lot of people we’ll cover them off in a future post.
Repositories are managed via the RepositoryFactory (which implements IRepositoryFactory and is determined via dependency injection) which also manages their connection strings. This means again that both the user of a repository, and the designer of the repository, do not need to worry about how the connection to a specific database or data store is configured, but can leave that up to the factory to look after this on their behalf.
The conversion of models to and from different shapes
Within a repository it is not unusual to need to take data that’s represented in one class, and convert it into another class. To give a concrete example, the data may be loaded by the entity framework into a proxy class, but we want to convert that into our more portable model definition which is a plain old code object.
To do this we provide a Model Converter using an IModelConverter interface. This converter is capable of receiving a model in any format (a Dictionary, a code class, etc) and mapping it across to a new format or type. Later when we need it we can use the same converter to to provide the reverse conversion.
As well as the IModelConverter interface a concrete default implementation is provided called simply ModelConverter. This default implementation will be used unless an alterative is supplied using dependency injection.
The model converter is most useful within repositories, but is also used elsewhere in the framework, and can be used in your own code.
If you had a method that could operate on any class so long as it had a Boolean “Live” property you can define that “shape” for the model, and use the model converter to convert any compatible model into the shape for you to use, and then back again.
The command layer for example uses the model converter like this when working out how to supply views or models into a command in the compatible type for the command to operate on. We’ll cover the command layer in detail in the fourth post in this series.
The model converter is also responsible for changing the shape of a model between formats. For example if we receive a Json representation of a model from a web service the model converter can turn that into a class for us. When the time comes to pass it back over the web service, we can simply convert it back. This is actually exactly how the JsonRepository webservice repository works to extend repository support to platforms it wouldn’t otherwise reach.
Because of the shape changing ability of models, and the flexibility of the built in repositories, we are able to use the Rapid Prototyping techniques we’ve pioneered to generate models in a portable shape from Entity Framwork .edmx files or other data sources, to make sure you never have duplicated code that needs to be maintained. We’ll have a series of articles on using Rapid Prototyping with Mvpc in the future.
The mark-up of metadata against models to provide hints to users of a model
For most purposes a model that simply contains data is all we need. But there are times when a name, value, and type don’t quite go far enough. The most obvious example is when showing data on screen. If a property in a model represents a foreign key linking relational data to another repository, do we really want to force the GUI layer for each platform to have to customise the GUI just to provide this link as a selection list or combobox? Wouldn’t it be better to mark the property with the repository needed for a selection list, and then let the GUI sort itself out based on this information?
Metadata is used to identify primary keys, display labels, repositories for foreign keys, required fields, special formatting requirements, and a host of other useful things.
In the Mvpc libraries the metadata API consists of an IModelMetadata interface, along with a concrete implementation that has some hard coded defaults based on attributes and recommended Microsoft naming conventions. This can be overridden using dependency injection for any individual model, or for all models.
A good API shouldn’t require sub classing or overriding too often, so the default implementation also provides the GuiHintAttribute that can be used to mark-up any property of a model to change the metadata for a property without having to create our own IModelMetadata classes. These are simple to use and would often look like:
[GuiHints( Required = true )] public string Name { get; set; } [GuiHints( Required = true, Repository = typeof(Customers.IRepository), UseMultiColumnDropDown = true )] public Guid CustomerFK { get; set; }
These attributes can either be put directly on a model, or in a separate class linked with the MetadataForAttribute.
Personally I have a strong preference for using the MetadataForAttribute as it not only keeps the metadata separate to the model definition, but also allows us to use the same metadata for multiple models if required. For example here is the code for the most common case where the same metadata file is shared between a model, and its repositories, so the metadata applies both when editing the model, and when displaying a the results of GetDisplayList(), which you will recall aren’t actually instances of the model itself:
[ModelMetadataFor(typeof(Models.Department))] [ModelMetadataFor(typeof(IRepository))] public class Metadata { [GuiHints( Required = true )] public object Name { get; set; } [GuiHints( Required = true, Repository = typeof(Customers.IRepository), UseMultiColumnDropDown = true )] public object CustomerFK { get; set; } [GuiHints( ListGroupLevel = 1 )] public object Customer { get; set; } }
Its worth noting in the above code that the return type of all the properties in the metadata class is “object”. This is by convention when writing metadata classes for Mvpc because the model class itself already exposes the type so repeating it here would only increase our maintenance costs or at the very least be misleading should we choose to change the type of one of the properties in the future.
We could set the return type of properties in metadata only classes to anything as they are never actually used, and the classes are never instantiated. Setting them to anything except the real type, or object, could mislead people reading your code however, so its considered best good practice to follow the DRY (don’t repeat yourself) principle here and always use “object”.
Conclusion
When designing the Model layer of the Mvpc design pattern and its commercially available reference library we wanted to make sure we didn’t restrict developers from choosing any of the very good data abstraction platforms already available. We did this by choosing a very simple definition of a model as a property bag.
To provide maximum flexibility on how the data was then used we allowed the data to change shape and pass between web services, databases, files, and commands in the shape that best suited the code being written in each of those areas, and in a way seamless to the developer consuming the model.
A flexible repository API with a lot of standard implementations and full rapid prototyping support allows us to expose a small and standard API for data operations ensuing the code we write never becomes dependent on a particular database or data storage engine or style.
Finally by allow metadata to be attached to models we are able to give hints to the GUI and other code on how to make the most of the model; allowing us to minimise the platform specific code needed for a model to be as small as possible. When we need to make changes to model metadata in the future we can carry out the change in a single place and have that change reflected across all platforms.
Hope this detailed overview has given you a good understanding of the model layer in the Mvpc design pattern and libraries. The next post in the series should be out next week and will look at the view layer in similar detail.
