A reference architecture (part 9)

…continued from part 8

Throwing business faults

In the previous blog posts I set up a working solution, but I didn’t take into account that sometimes things can go wrong. And it is reality that things most definitely will go wrong! That means that during our business logic, we need a way to throw business exceptions.

In the ArchitectureExample.Service.Shared project, add the following classes:

[AttributeUsage(AttributeTargets.Class)]
public class GlobalExceptionHandlerBehaviourAttribute : Attribute, IServiceBehavior
{
private readonly IErrorHandler _errorHandler;

public GlobalExceptionHandlerBehaviourAttribute()
{
// Although we should always use constructor injection, in this case we have no choice to use ObjectFactory directly
// because we cannot change the default constructor as it is used as an attribute.
_errorHandler = ObjectFactory.GetInstance<IErrorHandler>();
}

public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase) { }
public void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters) { }

public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers)
{
var channelDispatcher = dispatcherBase as ChannelDispatcher;
if (channelDispatcher != null) channelDispatcher.ErrorHandlers.Add(_errorHandler);
}
}
}

public class BusinessException : Exception
{
public string ErrorCode { get; set; }

public BusinessException(string errorCode, string message)
: base(message)
{
ErrorCode = errorCode;
}
}

[DataContract]
public class BusinessFault
{
[DataMember]
public string ErrorCode { get; set; }
[DataMember]
public string Message { get; set; }

public BusinessFault(string errorCode, string message)
{
ErrorCode = errorCode;
Message = message;
}
}

public class GlobalExceptionHandler : IErrorHandler
{
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
MessageFault msgFault;
if (error is BusinessException)
{
var businessError = error as BusinessException;
var businessFault = new BusinessFault(businessError.ErrorCode, businessError.Message);
var faultException = new FaultException<BusinessFault>(businessFault, "Oops", new FaultCode("BusinessFault"));
msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, error.TargetSite.Name);
}
else
{
FaultException faultException = new FaultException(string.Format("{0}", error.TargetSite.Name));
msgFault = faultException.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, faultException.Action);
}
}

public bool HandleError(Exception error)
{
return true;
}
}

Then decorate the service implementation in the ArchitectureExample.Service.Adapter project with the GlobalExceptionHandlerBehaviour attribute, for example:

[GlobalExceptionHandlerBehaviour]
public class ArchitectureExampleService : IArchitectureExampleService
{
}

Also decorate each service operation with the [FaultContract] attribute in the IArchitectureExampleService interface in the ArchitectureExample.Service.Contracts project:

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

[OperationContract]
[FaultContract(typeof(BusinessFault))]
AddCommentToBlogEntryResponse AddCommentToBlogEntry(AddCommentToBlogEntryRequest request);

[OperationContract]
[FaultContract(typeof(BusinessFault))]
void ModifyBlogEntry(ModifyBlogEntryRequest request);

[OperationContract]
[FaultContract(typeof(BusinessFault))]
void PublishBlogEntry(PublishBlogEntryRequest request);

[OperationContract]
[FaultContract(typeof(BusinessFault))]
void UnpublishBlogEntry(UnpublishBlogEntryRequest request);

[OperationContract]
[FaultContract(typeof(BusinessFault))]
FindAllBlogEntriesResponse FindAllBlogEntries();

[OperationContract]
[FaultContract(typeof(BusinessFault))]
FindBlogEntryDetailsResponse FindBlogEntryDetails(FindBlogEntryDetailsRequest request);
}

Then add the following registration in the StructureMapRegistry in the ArchitectureExample.Service.WebHost project:

For<IErrorHandler>().Use<GlobalExceptionHandler>();

If you now throw a BusinessException somewhere in the service code, the global exception handler will automatically convert the business exception to a BusinessFault and throw it so that service consumers can catch it.

To quickly test this, throw a business exception in the AddBlogEntryRequestHandler:

public class AddBlogEntryRequestHandler : IRequestHandler<AddBlogEntryRequest, AddBlogEntryResponse>
{
private readonly IUnitOfWork _unitOfWork;
private readonly IBlogEntryRepository _blogEntryRepository;

public AddBlogEntryRequestHandler(IUnitOfWork unitOfWork, IBlogEntryRepository blogEntryRepository)
{
_unitOfWork = unitOfWork;
_blogEntryRepository = blogEntryRepository;
}

public AddBlogEntryResponse Execute(AddBlogEntryRequest request)
{
throw new BusinessException("FISH", "Something fishy happened!");
var blogEntry = new BlogEntry(request.Title, request.Body);
_blogEntryRepository.Add(blogEntry);
_unitOfWork.Commit();
return new AddBlogEntryResponse(blogEntry.Id);
}
}

And add a new test:

[TestMethod]
public void AddBlogEntryWithFaultTest()
{
string errorCode = null, faultMessage = null;
using (var architectureExampleServiceAgent = new ArchitectureExampleServiceAgent("BasicHttpBinding_IArchitectureExampleService"))
{
try
{
var response = architectureExampleServiceAgent.AddBlogEntry(new AddBlogEntryRequest()
{
Title = "Blog entry title",
Body = "Blog entry body"
});
}
catch (FaultException<BusinessFault> fex)
{
errorCode = fex.Detail.ErrorCode;
faultMessage = fex.Detail.Message;
}
}
Assert.IsNotNull(errorCode);
Assert.IsNotNull(faultMessage);
}

Next time we’ll handle validation of requests and domain entities.

Advertisements

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