A reference architecture (part 6)

…continued from part 5

The service layer

Now it’s time to expose the application layer to the outside world.

Add a new class library project called ArchitectureExample.Service.Contracts to the Service Layer solution folder, and put it physically in the \Trunk\Sources\ folder:

p6_1

Add the service interface IArchitectureExampleService:

[ServiceContract]
public interface IArchitectureExampleService
{
[OperationContract]
AddBlogEntryResponse AddBlogEntry(AddBlogEntryRequest request);

[OperationContract]
AddCommentToBlogEntryResponse AddCommentToBlogEntry(AddCommentToBlogEntryRequest request);

[OperationContract]
void ModifyBlogEntry(ModifyBlogEntryRequest request);

[OperationContract]
void PublishBlogEntry(PublishBlogEntryRequest request);

[OperationContract]
void UnpublishBlogEntry(UnpublishBlogEntryRequest request);
}

Add a new class library project called ArchitectureExample.Service.Adapter to the Service Layer solution folder, and put it physically in the \Trunk\Sources\ folder:

p6_2

Add the service implementation:

public class ArchitectureExampleService : IArchitectureExampleService
{
public AddBlogEntryResponse AddBlogEntry(AddBlogEntryRequest request)
{
throw new NotImplementedException();
}

public AddCommentToBlogEntryResponse AddCommentToBlogEntry(AddCommentToBlogEntryRequest request)
{
throw new NotImplementedException();
}

public void ModifyBlogEntry(ModifyBlogEntryRequest request)
{
throw new NotImplementedException();
}

public void PublishBlogEntry(PublishBlogEntryRequest request)
{
throw new NotImplementedException();
}

public void UnpublishBlogEntry(UnpublishBlogEntryRequest request)
{
throw new NotImplementedException();
}
}

Before we can delegate the requests to the application layer, we need a mechanism to automatically execute the correct request handler, based on request/response objects. To do that, we will create a request handler dispatcher.

First add a new class library project called ArchitectureExample.Service.Shared to the Service Layer solution folder, and put it physically in the \Trunk\Sources\ folder:

p6_3

Add the interface IHandlerDispatcher:

public interface IHandlerDispatcher
{
TResponse Execute<TRequest, TResponse>(TRequest request, bool asynchronously)
where TRequest : IRequest, new()
where TResponse : IResponse, new();
void Execute<TRequest>(TRequest request, bool asynchronously) where TRequest : IRequest, new();
}

And also its implementation:

public class HandlerDispatcher : IHandlerDispatcher
{
public TResponse Execute<TRequest, TResponse>(TRequest request, bool asynchronously)
where TRequest : IRequest, new()
where TResponse : IResponse, new()
{
if (asynchronously) Task.Factory.StartNew(() => Execute<TRequest, TResponse>(request, false));

Type openType = typeof(IRequestHandler<,>); //generic open type
var type = openType.MakeGenericType(typeof(TRequest), typeof(TResponse)); // type is your runtime type
var requestHandler = ObjectFactory.GetInstance(type);
var methodInfo = type.GetMethod("Execute");
try
{
return (TResponse)methodInfo.Invoke(requestHandler, new[] { (object)request });
}
catch (Exception ex)
{
// Rethrow the inner exception instead of the invocation exception
throw ex.InnerException;
}
}

public void Execute<TRequest>(TRequest request, bool asynchronously)
where TRequest : IRequest, new()
{
if (asynchronously) Task.Factory.StartNew(() => Execute<TRequest>(request, false));

Type openType = typeof(IRequestHandler<>); //generic open type
var type = openType.MakeGenericType(typeof(TRequest)); // type is your runtime type
var requestHandler = ObjectFactory.GetInstance(type);
var methodInfo = type.GetMethod("Execute");
try
{
methodInfo.Invoke(requestHandler, new[] { (object)request });
}
catch (Exception ex)
{
// Rethrow the inner exception instead of the invocation exception
throw ex.InnerException;
}
}
}

As you see we use StructureMap’s ObjectFactory to get the correct instance of the request handler, so download StructureMap, put the StructureMap.dll assembly in the \Trunk\Libraries\StructureMap folder and add a reference to it from the ArchitectureExample.Service.Shared project (or install the nuget package).

Now we can implement the ArchitectureExampleService in the ArchitectureExample.Service.Adapter project:

public class ArchitectureExampleService : IArchitectureExampleService
{
private readonly IHandlerDispatcher _handlerDispatcher;

public ArchitectureExampleService(IHandlerDispatcher handlerDispatcher)
{
_handlerDispatcher = handlerDispatcher;
}

public AddBlogEntryResponse AddBlogEntry(AddBlogEntryRequest request)
{
return _handlerDispatcher.Execute<AddBlogEntryRequest, AddBlogEntryResponse>(request, false);
}

public AddCommentToBlogEntryResponse AddCommentToBlogEntry(AddCommentToBlogEntryRequest request)
{
return _handlerDispatcher.Execute<AddCommentToBlogEntryRequest, AddCommentToBlogEntryResponse>(request, false);
}

public void ModifyBlogEntry(ModifyBlogEntryRequest request)
{
_handlerDispatcher.Execute<ModifyBlogEntryRequest>(request, false);
}

public void PublishBlogEntry(PublishBlogEntryRequest request)
{
_handlerDispatcher.Execute<PublishBlogEntryRequest>(request, false);
}

public void UnpublishBlogEntry(UnpublishBlogEntryRequest request)
{
_handlerDispatcher.Execute<UnpublishBlogEntryRequest>(request, false);
}
}

Now, based on the request/response, the dispatcher calls the correct operation in the application layer.

We also need something to host the service, so add a new WCF service application project called ArchitectureExample.Service.WebHost to the Service Layer solution folder, and put it physically in the \Trunk\Sources\ folder:

p6_4

In the project delete all files except web.config. Add a new file called ArchitectureExampleService.svc:

<%@ ServiceHost Language="C#" Debug="true" Service="ArchitectureExample.Service.Adapter.ArchitectureExampleService" Factory="ArchitectureExample.Service.WebHost.StructureMapServiceHostFactory" %>

Add following classes to ArchitectureExample.Service.Shared project, which will allow the service to work nicely together with StructureMap:

public class StructureMapInstanceProvider : IInstanceProvider
{
private readonly Type _serviceType;

public StructureMapInstanceProvider(Type serviceType)
{
_serviceType = serviceType;
}

public object GetInstance(InstanceContext instanceContext)
{
return GetInstance(instanceContext, null);
}

public object GetInstance(InstanceContext instanceContext, Message message)
{
return ObjectFactory.GetInstance(_serviceType);
}

public void ReleaseInstance(InstanceContext instanceContext, object instance)
{
}
}

public class StructureMapServiceBehavior : IServiceBehavior
{
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}

public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}

public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
// For all of the endpoints in all of the channels, we need to give WCF the StructureMap instance provider.
// Also, since StructureMap needs to know what requestType to create, we have to pass that information along to our instance provider.
foreach (var endpointDispatcher in serviceHostBase.ChannelDispatchers.OfType<ChannelDispatcher>().SelectMany(channelDispatcher => channelDispatcher.Endpoints))
{
endpointDispatcher.DispatchRuntime.InstanceProvider = new StructureMapInstanceProvider(serviceDescription.ServiceType);
}
}
}

public class StructureMapServiceHost : ServiceHost
{
public StructureMapServiceHost()
{
}
public StructureMapServiceHost(Type serviceType, params Uri[] baseAddresses)
: base(serviceType, baseAddresses)
{
}
protected override void OnOpening()
{
Description.Behaviors.Add(new StructureMapServiceBehavior());
base.OnOpening();
}
}

public abstract class BaseStructureMapServiceHostFactory : ServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return new StructureMapServiceHost(serviceType, baseAddresses);
}
}

Then add following classes to ArchitectureExample.Service.WebHost project, so that the required dependencies are registered correctly:

public class StructureMapRegistry : Registry
{
public StructureMapRegistry()
{
For<IArchitectureExampleService>().Use<ArchitectureExampleService>();
For<IHandlerDispatcher>().Use<HandlerDispatcher>();

Scan(scanner =>
{
scanner.TheCallingAssembly();
scanner.AssemblyContainingType<AddBlogEntryRequestHandler>();
scanner.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>));
scanner.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<>));
scanner.WithDefaultConventions().OnAddedPluginTypes(apt => apt.HybridHttpOrThreadLocalScoped());
});

For<IDatabaseFactory<EfArchitectureExampleDbContext>>().HybridHttpOrThreadLocalScoped().Use<DatabaseFactory<EfArchitectureExampleDbContext>>();
For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<UnitOfWork>();
For<IBlogEntryRepository>().HybridHttpOrThreadLocalScoped().Use<BlogEntryRepository>();
}
}

public class StructureMapBootstrapper : IBootstrapper
{
public void BootstrapStructureMap()
{
ObjectFactory.Initialize(x =>
x.Scan(scanner =>
{
// Include the following assemblies in the scanning process and look for registries
scanner.TheCallingAssembly();
scanner.AssemblyContainingType<StructureMapRegistry>();
scanner.LookForRegistries();
}));
}

public static void Bootstrap()
{
new StructureMapBootstrapper().BootstrapStructureMap();
}
}

public class StructureMapServiceHostFactory : BaseStructureMapServiceHostFactory
{
public StructureMapServiceHostFactory()
{
// Put bootstrapper initialization here
StructureMapBootstrapper.Bootstrap();
}
}

Configure the service in the web.config file:

<?xml version="1.0"?>
<configuration>

<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>

<connectionStrings>
<add name="EfArchitectureExampleDbContext" connectionString="Data Source=.;Initial Catalog=ArchitectureExample;Persist Security Info=True;User ID=architectureexample;Password=architectureexample" providerName="System.Data.SqlClient"/>
</connectionStrings>

<system.serviceModel>

<services>
<service behaviorConfiguration="serviceBehavior" name="ArchitectureExample.Service.Adapter.ArchitectureExampleService">
<endpoint address="" binding="basicHttpBinding" contract="ArchitectureExample.Service.Contracts.IArchitectureExampleService" bindingConfiguration="basicHttpBinding">
<identity>
<dns value="" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>

<bindings>
<basicHttpBinding>
<binding name="basicHttpBinding" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
</security>
</binding>
</basicHttpBinding>
</bindings>

<behaviors>
<serviceBehaviors>
<behavior name="serviceBehavior">
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior>
</serviceBehaviors>
</behaviors>

<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>

<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>

</configuration>

Almost there… Only thing left is the service agent. The service agent abstracts the service usage for the consumer of the service, which makes it easier to use; but it also shields other concerns like logging and caching.

Add a new class library project called ArchitectureExample.Service.Agent to the Service Layer solution folder, and put it physically in the \Trunk\Sources\ folder:

p6_5

First add the following class to the ArchitectureExample.Service.Shared project:

public class ServiceProxyBase<T> : IDisposable where T : class
{
private readonly string _serviceEndpointName;
private readonly object _sync = new object();
private ChannelFactory<T> _channelFactory;
private T _channel;
private bool _disposed = false;

protected ServiceProxyBase(string serviceEndpointName)
{
_serviceEndpointName = serviceEndpointName;
}

protected T Channel
{
get { EnsureChannel(); return _channel; }
}

public void EnsureChannel()
{
if (_disposed) throw new ObjectDisposedException("ServiceProxyBase<" + typeof(T) + "> has been disposed");
lock (_sync)
{
if (_channel != null) return;
_channelFactory = new ChannelFactory<T>(_serviceEndpointName);
//_channel = _channelFactory.CreateChannel(new EndpointAddress(_serviceEndpointUri));
_channel = _channelFactory.CreateChannel();
}
}

~ServiceProxyBase()
{
Dispose(false);
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

private void Dispose(bool disposeManaged)
{
if (_disposed) return;
if (disposeManaged)
{
DisposeChannel();
}
_disposed = true;
}

private void DisposeChannel()
{
if (_channel == null) return;
lock (_sync)
{
if (_channel == null) return;
var communicationObject = (object)_channel as ICommunicationObject;
if (communicationObject == null) return;
var success = false;
try
{
if (communicationObject.State != CommunicationState.Faulted)
{
communicationObject.Close();
success = true;
}
}
finally
{
if (!success) { communicationObject.Abort(); }
_channel = default(T);
}
}
}
}

And then add the service agent to the the ArchitectureExample.Service.Agent project:

public class ArchitectureExampleServiceAgent : ServiceProxyBase<IArchitectureExampleService>, IArchitectureExampleService
{
public ArchitectureExampleServiceAgent(string serviceEndpointName)
: base(serviceEndpointName)
{
}

public AddBlogEntryResponse AddBlogEntry(AddBlogEntryRequest request)
{
return Channel.AddBlogEntry(request);
}

public AddCommentToBlogEntryResponse AddCommentToBlogEntry(AddCommentToBlogEntryRequest request)
{
return Channel.AddCommentToBlogEntry(request);
}

public void ModifyBlogEntry(ModifyBlogEntryRequest request)
{
Channel.ModifyBlogEntry(request);
}

public void PublishBlogEntry(PublishBlogEntryRequest request)
{
Channel.PublishBlogEntry(request);
}

public void UnpublishBlogEntry(UnpublishBlogEntryRequest request)
{
Channel.UnpublishBlogEntry(request);
}
}

The service agent also implements the service interface; which means that if you change the service or add operations, you must also change the service agent so that service and agent are kept in sync.

That completes the service layer, that now looks like:

p6_6

Next time we will write a test to see if everything works!

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.