Distributed Entity Programming
If the design of your distributed system is intending to provide a tight coupling between client and service by sharing your domain model then you will want to expose your entities directly either by hand crafted WCF services or by using a DistributedUnitOfWork to expose your service based UnitOfWork to your clients. If you are intending to enforce a clean separation across service boundaries then you will want to implement an approach using Data Transfer Objects and should review the associated section below on using Data Transfer Objects with LightSpeed.
The Distributed Unit of Work
In our chapter on Basic Operations we introduced the unit of work. A unit of work is used to scope your business transactions and manage loading, tracking and persisting entities. In a distributed scenario you are disconnected from the database so a LightSpeed unit of work is no longer valid, however from a design perspective the pattern remains valid. So to assist with distributed scenarios LightSpeed has a distributed version of the unit of work which allows the client to locally track objects and then serialize changes back to the server when changes are to be persisted.
This distributed version of the unit of work, called DistributedUnitOfWork is found in the Mindscape.LightSpeed.ServiceModel assembly. You create a DistributedUnitOfWork instance in a similar fashion to instantiating a standard unit of work. This then creates a scope which will delegate its operations to a remote unit of work located at a known service endpoint. This affords you the convenience of programming as if you were dealing with a local unit of work while being disconnected from the database.
Instantiating a DistributedUnitOfWork instance |
// DistributedModelUnitOfWork is a strongly typed DistributedUnitOfWork class |
A distributed unit of work is represented in LightSpeed as the IDistributedUnitOfWork interface. IDistributedUnitOfWork mirrors the IUnitOfWork interface and provides an identical set of operations which allow you to load, add and remove entities, and to save pending changes. IDistributedUnitOfWork is only limited on operations which deal with database specific entities such as IDbReader and IDbCommand; in cases where functionality is not supported a NotSupportedException will be thrown and these specific methods have been noted in the XML documentation for the interface.
To create a DistributedUnitOfWork a LightSpeedContext object must be instantiated with the UnitOfWorkFactory set to a new instance of a DistributedUnitOfWorkFactory class. The factory is used to determine how to connect to the service endpoint using WCF and by default will initialize itself using application configuration based on the endpoint named LightSpeedDistributedUnitOfWorkEndpoint . Alternatively the factory can be instantiated with a WCF EndpointAddress and Binding if you require runtime configuration.
The DistributedUnitOfWorkService
When the DistributedUnitOfWork runs on the client, there needs to be a service endpoint capable of servicing requests targeting the model being used by the client. To host this there is a DistributedUnitOfWorkService class provided which allows you to host an endpoint using WCF.
There are two ways this could be hosted, either you will manually host an actual instance of the service within a dedicated process, for example a Console application or a Windows Service; or you will have the service activated by IIS using Windows Activation Services and a .svc file hosted in an ASP.NET website. Both approaches are supported.
Manually Hosting the Service
To host the service manually you need to first create a UnitOfWork instance which will be hosted by the service and then instantiate the service instance and pass that to the constructor of a WCF ServiceHost. This will create a single instance of the service hosting a single UnitOfWork which will be shared among all callers.
Manually hosting a DistributedUnitOfWorkService in single instance mode |
var context = new LightSpeedContext<FilmFestival.Model.ModelUnitOfWork>("default"); |
You will also need to specify configuration for the endpoint as part of your system.serviceModel configuration section. Here is an example using a basicHttpBinding. The contract which is used is the Mindscape.LightSpeed.ServiceModel.IDistributedUnitOfWorkContract.
Example configuration for a DistributedUnitOfWorkService hosted in single instance mode |
<system.serviceModel> |
If you wish to have a new instance of the DistributedUnitOfWorkService created on a per WCF instance basis, then you will need to subclass DistributedUnitOfWorkService, providing the appropriate LightSpeedContext as a constructor argument to the base class. An example of how to implement this is shown below.
Manually hosting a DistributedUnitOfWorkService in multi instance mode |
public class MyUnitOfWorkService : DistributedUnitOfWorkService |
As with hosting in single instance mode you will need to specify configuration about the endpoint. Here is an example. The key difference from the example above is that we need to ensure our service type name is specified in the name attribute for the service configuration. The address, binding and contract are all the same.
Example configuration for a DistributedUnitOfWorkService hosted in multi instance mode |
<system.serviceModel> |
Supported Bindings
Because the DistributedUnitOfWork has been built on WCF it should support any of the standard transports and bindings available within WCF. From a support perspective however the implementation has been developed and tested on only the Named Pipe, TCP and HTTP bindings.
Configuring Your Client for a DistributedUnitOfWork
Before you are able to receive results on your client you first need to configure the client to use a DistributedUnitOfWork instead of a standard UnitOfWork. While the configuration for a standard UnitOfWork revolves around assigning the connection string and database provider, the configuration required for a DistributedUnitOfWork focuses on the details of the endpoint that it needs to connect to.
The primary aspect of configuration that is required to use a DistributedUnitOfWork is to override the UnitOfWorkFactory property on your context with an instance of a DistributedUnitOfWorkFactory class. Any runtime details about the endpoint can be configured through the constructor for this class, otherwise it is assumed you have a system.serviceModel endpoint named LightSpeedDistributedUnitOfWorkEndpoint configured.
Example of creating a DistributedUnitOfWorkFactory class and a DistributedUnitOfWork |
var context = new LightSpeedContext<DistributedModelUnitOfWork>() |
Example configuration you would specify in system.serviceModel |
<system.serviceModel> |
Example of creating a DistributedUnitOfWorkFactory with runtime configuration about the endpoint |
var context = new LightSpeedContext<DistributedModelUnitOfWork>() |
Example of creating a DistributedUnitOfWorkFactory with a typed model |
public interface IMyTypedUnitOfWork |
Executing Operations at the Client
Once you have configured your client to use a DistributedUnitOfWork you can create a UnitOfWork in the standard manner by calling LightSpeedContext.CreateUnitOfWork(). Subsequent calls to execute queries will trigger a service call as will calls to SaveChanges().
Data Contracts
If the project containing your LightSpeed model references System.ServiceModel then the Visual Studio designer will automatically emit DataContract and DataMember attributes to mark up your model classes allowing them to be correctly serialized by the WCF DataContract formatter.
By default all value properties are marked with a DataMember attribute including foreign key identity fields, but all associations are omitted from serialization. This is to allow you to more correctly specify what structures should be serialized in distributed scenarios rather than potentially opting in the whole domain model for serialization.
It is highly recommended that you review which associations should be serialized when designing your model for distributed scenarios.
Building WCF Services using Entities
If you don’t wish to use a DistributedUnitOfWork to remote your entities between your client and service then the other approach which can be used is to expose your entities using a hand crafted WCF service.
Review the section above regarding Data Contracts and ensure that you have designed your model for the distributed scenarios you wish to support. Unlike with a Data Transfer Object approach, because you have a single model to work with you will not be able to tailor your messages on a per request basis so we would recommend that you avoid prematurely opting in associations to your DataContracts.
If you choose to include EntityCollections into serialization you will need to use a special version of the DataContract formatter known as the NetDataContractFormatter which allows correct serialization of collection types which have been marked with a DataContract attribute.
Samples
There are two samples available which make use of the DistributedUnitOfWork which will give you a practical view of how the DistributedUnitOfWork can be used across the two standard types of applications you are likely to be using it with.
The first sample is the ATM sample within which the Teller project is an example of a WPF application which uses the DistributedUnitOfWork as part of a long running stateful client application.
The second sample is the Film Festival sample within which the Website uses the DistributedUnitOfWork in a per request fashion as part of a stateless ASP.NET MVC web application.
For a sample which demonstrates building a hand crafted WCF service which exposes entities then you should review the ATM sample. The ATMClient console application project makes use of several service calls which deal with entities which have been shared with the Website service.