Hide complexity and inconsistency of legacy classes using facade

May 22nd, 2010 by ganton | Print

Very often in a system we may have modules that are implemented by different persons for a long period of time that are inconsistent and it is difficult to use them in a new implementation. Here in this post you can find a sample of solving these problems using facade design pattern, unique interface and IoC.

Let’s assume that we have a set of report building classes that have no common interface but they need similar data in order to build different report views. These are the sample legacy classes that we need to use.

   1: public class BanchmarkReportBuilder

   2: {

   3:     public Stream CreateBenchmarkReport(Data data, AnotherData anotherData, ReportType type)

   4:     {

   5:         return null;

   6:     }

   7: }

   8:  

   9: public class MonitoringReportBuilder

  10: {

  11:     public Stream CreateMonitoringReport(int dataId, int anotherDataId)

  12:     {

  13:         return null;

  14:     }

  15: }

  16:  

  17: public class AdditionalReportBuilder

  18: {

  19:     public Stream CreateAdditionalReport(Data data, int anotherDataId, IList<int> someIds, ReportType type)

  20:     {

  21:         return null;

  22:     }

  23: }

Here are the sample data classes.

   1: public class Data

   2: {

   3:     public int Id { get; set; }

   4:     public string Info { get; set; }

   5: }

   6:  

   7: public class AnotherData

   8: {

   9:     public int Id { get; set; }

  10:     public IList<object> Data { get; set; }

  11: }

  12:  

  13: public enum ReportType

  14: {

  15:     Word = 1,

  16:     Pdf = 2

  17: }

  18:  

  19: public class ReportData

  20: {

  21:     public int Id { get; set; }

  22:     public string Name { get; set; }

  23:     public string ClassReferenceName { get; set; }

  24: }

It is obvious how they were used. Everywhere the same data retrieved in different web pages and then supplied to the report classes. So, we have these old not very nice implementations and they are used even “better”. Let’s say that at some point it is required to have an automated system that will send e-mails with link(s) to a specific report(s) and the user should be able to click on the link and to go to a general report page. This page should somehow build the report and send it back to the end-user. But it may be a problem with the implementations demonstrated above. For sure, we can re-factor all the implementations and then their usage and then to use them in our new implementation but this will cost us plenty of time. In order to reduce the time of the implementation we can skip re-factoring part and to build an unique interface around these legacy classes and to implement just the simple method of the unique interface. This action can be assumed as re-factoring :) .Below is the interface and its implementation in legacy classes.

   1: public interface IReportBuilder

   2: {

   3:     Stream Build(Data data, AnotherData anotherData, ReportType type, params int[] someIds);

   4: }

   5:  

   6: public class BanchmarkReportBuilder : IReportBuilder

   7: {

   8:     public Stream CreateBenchmarkReport(Data data, AnotherData anotherData, ReportType type)

   9:     {

  10:         return null;

  11:     }

  12:  

  13:     public Stream Build(Data data, AnotherData anotherData, ReportType type, params int[] someIds)

  14:     {

  15:         return CreateBenchmarkReport(data, anotherData, type);

  16:     }

  17: }

  18:  

  19: public class MonitoringReportBuilder : IReportBuilder

  20: {

  21:     public Stream CreateMonitoringReport(int dataId, int anotherDataId)

  22:     {

  23:         return null;

  24:     }

  25:  

  26:     public Stream Build(Data data, AnotherData anotherData, ReportType type, params int[] someIds)

  27:     {

  28:         return CreateMonitoringReport(data.Id, anotherData.Id);

  29:     }

  30: }

  31:  

  32: public class AdditionalReportBuilder : IReportBuilder

  33: {

  34:     public Stream CreateAdditionalReport(Data data, int anotherDataId, IList<int> someIds, ReportType type)

  35:     {

  36:         return null;

  37:     }

  38:  

  39:     public Stream Build(Data data, AnotherData anotherData, ReportType type, params int[] someIds)

  40:     {

  41:         return CreateAdditionalReport(data, anotherData.Id, someIds, type);

  42:     }

  43:  

  44: }

What we have now is an unique interface and a very simple implementation of the interface’s method. Actually, the implementation of Build method in all of the legacy classes is just a wrapper around existing legacy method. The next problem that we need to solve is how the new general implementation will now which builder class to instantiate. In my case we use IoC container where it is possible declaratively to specify classes in a XML file configuration. Let’s say that we have a class like those below that holds a general instance of IoC container for all application.

   1: public static class IoCInstance

   2: {

   3:     public static TResult GetObject<TResult>(string name)

   4:     {

   5:         return default(TResult);

   6:     }

   7: }

Next question to answer is how we will know which of the reports we need to build. Fortunately, we have a report meta-data stored in the database. For example, a table with columns id, name, timestamp and classReferenceName is more then enough for this sample. In a real case, you will have more tables and more info based on your needs. We also need to have some repository classes that will retrieve the information from the database. All these sample classes are below.

   1: public interface IReportRepository

   2: {

   3:     ReportData GetReport(int id);

   4: }

   5:  

   6: public interface IDataRepository

   7: {

   8:     Data GetData(int id);

   9:     AnotherData GetAnotherData(int id);

  10: }

  11:  

  12: public class ReportRepository

  13: {

  14:     public ReportData GetReport(int id)

  15:     {

  16:         return new ReportData { Id = 1, Name = "Banchmark Report", ClassReferenceName = "BanchmarkReportBuilder" };

  17:     }

  18: }

  19:  

  20: public class DataRepository

  21: {

  22:     public Data GetData(int id)

  23:     {

  24:         return new Data { Id = 1, Info = "info" };

  25:     }

  26:  

  27:     public AnotherData GetAnotherData(int id)

  28:     {

  29:         return new AnotherData { Id = 1, Data = "data" };

  30:     }

  31: }

As we discussed above using the IReportBuilder interface we solve the inconsistence and complexity of all legacy classes. Having reports’ meta-data in the database we can add in the link a parameter containing report id. Then in the general page we will use it to receive all the information that we need for a specific report. Then having the IoC we can use it to get the right builder instance. All of this is fine but we also want to hide the complexity of using all these different classes. To solve this we will use a facade class that will be able to return a stream of a generated report and the report name based on the general set of parameters that we will send with the link. A sample of a facade class is below.

   1: public interface IReportBiuldingFacade

   2: {

   3:     Stream Build(int reportId, int dataId, int anotherDataId, ReportType documentType, int[] answersetIds, out string reportName);

   4: }

   5:  

   6: public class ReportBiuldingFacade

   7: {

   8:     private IReportRepository reportRepository;

   9:     private IDataRepository dataRepository;

  10:     private string applicationRootPath;

  11:  

  12:     public ReportBiuldingFacade(IReportRepository reportRepository, IDataRepository dataRepository, string applicationRootPath)

  13:     {

  14:         this.reportRepository = reportRepository;

  15:         this.dataRepository = dataRepository;

  16:         this.applicationRootPath = applicationRootPath;

  17:     }

  18:  

  19:     public Stream Build(int reportId, int dataId, int anotherDataId, ReportType documentType, int[] answersetIds, out string reportName)

  20:     {

  21:  

  22:         ReportData reportData = reportRepository.GetReport(reportId);

  23:         Data data = dataRepository.GetData(dataId);

  24:         AnotherData anotherData = dataRepository.GetAnotherData(anotherDataId);

  25:  

  26:         IReportBuilder reportBuilder = IoCInstance.GetObject<IReportBuilder>(reportData.ClassReferenceName);

  27:  

  28:         reportName = reportData.Name.Trim() + "." + documentType.ToString().ToLower();

  29:  

  30:         return reportBuilder.Build(data, anotherData, documentType, answersetIds);

  31:     }

  32: }

And to make the sample complete below you can find a unit test class with methods for the facade. The unit test methods are implemented using NUnit framework and Rhino.Mocks framework. You can see that the tests target is the facade logic and all other classes are mocked. In order to be able to mock the repositories the IoC in the facade constructor was used. In order to abstract from the report builder a stub was created and declared in IoC container XML configuration file for the tests. The stub contains an Action that is used in the test in order to check that the stub method was executed. The first class is the stub and the next is the unit test for the facade.

   1: public class ReportBuilderStub : IReportBuilder

   2: {

   3:     public System.Action MethodExecuted;

   4:  

   5:     public Stream Build(Data data, AnotherData anotherData, ReportType reportType, IList<int> somIds)

   6:     {

   7:         if (MethodExecuted != null)

   8:         {

   9:             MethodExecuted();

  10:         }

  11:  

  12:         return null;

  13:     }

  14: }

   1: [TestFixture]

   2: public class ReportBuildingFacadeTestFixture

   3: {

   4:     [Test]

   5:     [TestCase(false, true, null, ExpectedException = typeof(ArgumentNullException))]

   6:     [TestCase(true, false, null, ExpectedException = typeof(ArgumentNullException))]

   7:     [TestCase(true, true, null, ExpectedException = typeof(ArgumentNullException))]

   8:     [TestCase(true, true, "", ExpectedException = typeof(ArgumentOutOfRangeException))]

   9:     public void ConstructorTest(bool initializeReportRepository, bool initializeDataRepository, string path)

  10:     {

  11:         IReportRepository reportRepository = null;

  12:         IDataRepository dataRepository = null;

  13:         MockRepository repository = new MockRepository();

  14:  

  15:         if (initializeReportRepository)

  16:         {

  17:             reportRepository = repository.Stub<IReportService>();

  18:         }

  19:  

  20:         if (initializeDataRepository)

  21:         {

  22:             dataRepository = repository.Stub<IQuestionnaireReportService>();

  23:         }

  24:  

  25:         IReportBuildingFacade facade = new ReportBuildingFacade(reportRepository, dataRepository, path);

  26:     }

  27:  

  28:     [Test]

  29:     [TestCase(0, 1, 1, ExpectedException = typeof(ArgumentOutOfRangeException))]

  30:     [TestCase(1, 0, 1, ExpectedException = typeof(ArgumentOutOfRangeException))]

  31:     [TestCase(1, 1, 0, ExpectedException = typeof(ArgumentOutOfRangeException))]

  32:     public void BuildWithWrongParametersTest(int reportId, int dataId, int anotherDataId)

  33:     {

  34:         MockRepository repository = new MockRepository();

  35:         IReportRepository reportRepository = repository.Stub<IReportRepository>();

  36:         IDataRepository dataRepository = repository.Stub<IDataRepository>();

  37:  

  38:         IReportBuildingFacade facade = new ReportBuildingFacade(reportRepository, dataRepository, "path");

  39:  

  40:         string reportName;

  41:         facade.Build(reportId, dataId, anotherDataId, ReportType.Pdf, new int[] { 1 }, out reportName);

  42:     }

  43:  

  44:     [Test]

  45:     [TestCase(1, 1, 1, (int)DocumentType.Pdf)]

  46:     public void BuildTest(int reportId, int dataId, int anotherDataId, int reportTypeId)

  47:     {

  48:         MockRepository repository = new MockRepository();

  49:         IReportRepository reportRepository = repository.StrictMock<IReportRepository>();

  50:         Expect.Call(reportRepository.GetReport(1)).Return(

  51:             new ReportData

  52:             {

  53:                 Id = reportId,

  54:                 Name = "report",

  55:                 ClassReferenceName = "BanchmarkReportBuilder",

  56:             });

  57:         IDataRepository dataRepository = repository.StrictMock<IDataRepository>();

  58:         Expect.Call(dataRepository.GetData(dataId))

  59:             .Return(new Data());

  60:         Expect.Call(dataRepository.GetAnotherData(anotherDataId))

  61:             .Return(new AnotherData());

  62:  

  63:         repository.ReplayAll();

  64:  

  65:         IReportBuildingFacade facade = new ReportBuildingFacade(reportRepository, dataRepository, "path");

  66:         bool buildMethodExecuted = false;

  67:  

  68:         ReportBuilderStub reportBuilder = IoCInstance.GetObject<ReportBuilderStub>("BanchmarkReportBuilder");

  69:         reportBuilder.MethodExecuted = () => buildMethodExecuted = true;

  70:  

  71:         string reportName;

  72:         facade.Build(reportId, dataId, anotherDataId, ReportType.Pdf, new int[] { 1 }, out reportName);

  73:  

  74:         repository.VerifyAll();

  75:  

  76:         Assert.AreEqual("report.pdf", reportName, "returned report name is not correct.");

  77:         Assert.True(buildMethodExecuted, "Build Method of the report builder is not executed.");

  78:     }

  79: }

Leave a Reply