Book Image

ServiceStack 4 Cookbook

Book Image

ServiceStack 4 Cookbook

Overview of this book

Table of Contents (18 chapters)
ServiceStack 4 Cookbook
Credits
About the Authors
About the Reviewers
www.PacktPub.com
Preface
Index

Managing dependencies with Funq and Inversion of Control (IoC)


Until now, ReidsonMessenger has stored all of its messages in a static List<Message> attached to MessengerService. This isn't a great design for a number of reasons—if our app restarts, all previous messages are lost. That's really just the beginning though; it's also not at all thread-safe. If two clients connect to our server and try to read or write messages at the same time, we're likely to see problems.

Let's refactor our code to fix the problem. The first thing we notice is that our existing implementation is a bit difficult to remove. We have a dependency on this static list—and all of our code in the MessengerService assumes that messages are stored in a List<Message> that we can access directly.

Let's start by making it easy to remove static List<Message> by wrapping it in a repository class. While we're at it, though, let's begin using Funq to inject this dependency to make our life easier—while we're experimenting with fixing the issue, we don't want to break the code.

This process of using IoC to replace an existing component with a new one while keeping the software working is sometimes referred to as "Branching by Abstraction". Instead of creating a separate feature branch of our software, we can keep working on the trunk, but get the benefits of creating our new implementation safely without interrupting production.

Getting ready

  1. First, we'll write a test that illustrates the problem we're trying to solve—that the current implementation isn't thread-safe.

  2. Since our theory is that the static list is at fault, we'll refactor the list so that it's not a field inside of our service anymore—we'll create a new class called StaticMessagesRepository, and move the static list into this new class. We'll make adjustments to our service to make calls to this class instead of accessing the list directly. We do this so that we can easily replace this working but flawed implementation with our new one.

  3. We'll create an interface that covers the most important functionality for StaticMessageRepository—the method to Add new messages—and another method that accepts Predicate<Message,bool> and returns IEnumerable<Message>. This allows us to take a dependency on this interface, not the specific implementation, in our service. That makes it easier for us to swap it in the AppHost.Configure method, allowing us to keep control over which implementation to use in that central location.

  4. Next, we'll start wiring up the messages repository with Funq. We'll set up our production AppHost class to wire up our existing but flawed StaticMessagesRepository, but we'll start working on a new implementation that leverages OrmLite. In our tests, we'll test the new OrmMessagesRepository until it solves the problem and is working. When we're through, we can simply change the AppHost.Configure method in our production AppHost class to use the newly tested implementation.

  5. Once all of our tests pass, and we've been in production a little while, we can remove the old StaticMessagesRepository as it has outlived its usefulness.

Note

For more information on the Branch by Abstraction technique, the canonical article can be found on Martin Fowler's site at http://martinfowler.com/bliki/BranchByAbstraction.html.

How to do It…

  1. Let's start by developing a unit test that shows the problem we're talking about. We can illustrate the problem with a simple Parallel.ForEach method that will run lots of commands against the list until it eventually throws an exception:

    [Test]
    public void ShouldBeThreadSafe()
    {
      var service = new MessengerService();
      const string testGroupName = "Main";
      const int iterations = 200;
    
      Parallel.ForEach(
        Enumerable.Range(1, iterations),
        iteration =>
        {
          service.Post(new Message
          {
            Body = "Post {0}".Fmt(iteration),
            Sender = "Sender",
            GroupName = testGroupName
          });
          service.Get(new Search
          {
            Group = testGroupName,
            Query = "Post"
          });
        });
                
      var testGroup = service.Get(new Group
      {
        GroupName = testGroupName
      });
                
      var randomSearchString = "Post {0}".Fmt(
        new Random().Next(1, iterations));
    
      Assert.AreEqual(1, testGroupMessages
        .Messages
        .Count(m => m.Body.Equals(randomSearchString)));
    }

    On the author's machine, this test fails with this exception:

    System.InvalidOperationException : Collection was modified; 
    enumeration operation may not execute. 

    This happens because the collection was modified by an Add method while we were trying to read from the collection, as expected.

  2. Since we think that the static List<Message> is the problem, we'll make changes to the original service to isolate it for removal. Instead of just having List<Message> as a field on MessengerService, we'll wrap it in a class, and then use this new class for access. This class will implement an IMessageRepository interface in order to allow us to easily replace it later with a better implementation. The code for this procedure is as follows:

    public interface IMessageRepository
    {
      void Add(Message message); 
      IEnumerable<Message> Where(Func<Message, bool> predicate);
    }
    public class StaticMessageRepository 
      : IMessageRepository
    {
      static List<Message> _messages = new List<Message>();
    
      public void Add(Message message)
      {
        _messages.Add(message);
      }
    
      public IEnumerable<Message> Where(Func<Message, bool> predicate)
      {
        return _messages.TakeWhile(predicate).ToList();
      }
    }
  3. Now we'll refactor our service to use the new wrapper class instead of directly accessing static List<T>. We call this change a refactor because the end result should be functionally equivalent; only the organization of the code has changed. We'll also change MessengerService to rely on Funq to provide IMessageRepository in the future:

    public class MessengerService : Service
    {
      public IMessageRepository MessageRepository { get; set; }
    
      public object Post(Message request)
      {
        MessageRepository.Add(request);
        return new MessageResponse { Message = "OK" };
      }
    
      public object Get(Group request)
      {
        return new GroupResponse
        {
          Messages = MessageRepository.Where(
            message => message
              .GroupName.Equals(request.GroupName))
          .ToList()
        };
      }
    
      public object Get(Search request)
      {
        return new SearchResponse
        {
          Messages = MessageRepository.Where(
            message => message
              .GroupName.Equals(request.Group)
              && message.Body.Contains(request.Query))
          .ToList()
        };
      }
    }
  4. Now, we'll create BasicOrmMessageRepository, a fresh, basic implementation using OrmLite, just enough to get it working. We can improve it with other refactoring later; it is important for now not to break our existing code. Here's how our code looks:

    public class BasicOrmMessageRepository : IMessageRepository
    {
      public IDbConnectionFactory DbConnectionFactory 
      { get; set; }
    
      public void Add(Message message)
      {
        using (var db = DbConnectionFactory.OpenDbConnection())
        {
          db.Insert(message);
        }
      }
    
      public IEnumerable<Message> Where(
        Func<Message, bool> predicate)
      {
        using (var db = DbConnectionFactory.OpenDbConnection())
        {
          var allDatabase = db.Select<Message>();
          var results = new List<Message>();
          allDatabase.ForEach(m =>
          {
            if (predicate(m)) results.Add(m);
          });
          return results;
        }
      }
    }

    An astute reader might have noticed that the database code shown previously really isn't a great OrmLite implementation. It's quite likely that your database performance tests are going to notice a big slowdown with this implementation once the service has more than a few thousand messages as our search code simply fetches the entire database of messages into memory, loops them over, and checks them one by one for a match.

    For now, we're forced into this implementation because we're trying to honor the existing contract, and OrmLite can't accept a raw predicate unlike our static list. OrmLite's Select() requires Expression<Func<Message,bool>> and not just Func<Message,bool>. This naive implementation gets us around that particular problem for now.

    The good thing about this naive implementation is that it is completely compatible with the old one, so we can keep working on the new one while we coexist with the old implementation. We'll need to solve this performance problem, of course—we'll come back to this before the recipe is up, refactoring as we go.

  5. Next, let's go into TestFixtureSetUp and wire up our new dependency. We'll also add code to a SetUp method that drops and recreates the table we'll use before any test. Once we're sure that the new implementation is better than our static list, we'll add similar code to AppHost:

    [TestFixtureSetUp]
    public void FixtureSetUp()
    {
      _appHost = new BasicAppHost
      {
        ConfigureContainer =
          container =>
            {
              container.RegisterAutoWiredAs<
                BasicOrmMessageRepository, IMessageRepository>();  
              container.RegisterAutoWired<
                MessengerService>();
              var dbFactory = new OrmLiteConnectionFactory(
                "~/App_Data/db.sqlite".MapHostAbsolutePath(), 
                SqliteDialect.Provider);
              container.Register<IDbConnectionFactory>(
                dbFactory);
            }
      }.Init();
    }
    
    [SetUp]
    public void SetUp()
    {
      using (var db = _appHost.Resolve<IDbConnectionFactory>()
      .OpenDbConnection())
      {
        db.DropAndCreateTable<Message>();
      }
    }
  6. As soon as we drop this code in place, however, our tests start having problems. It turns out that OrmLite doesn't like our use of .Equals() in the predicate. We'll have to change MessengerService to accommodate that using == instead, which it can handle. Here's how the code for the procedure described in this paragraph looks:

    public object Get(Group request)
    {
      return new GroupResponse
      {
        Messages = MessageRepository
        .Where(message => message.GroupName == request.GroupName)
        .ToList()
      };
    }
    
    public object Get(Search request)
    {
     return new SearchResponse
     {
       Messages = MessageRepository
           .Where(message => message.GroupName == request.Group && message.Body.Contains(request.Query))
       .ToList()
     };
    }
  7. With this code in place, the tests should now pass! Our OrmMessageRepository file solved the concurrency issue. Now it's time to go back and fix the performance issue.

  8. Again, we'll start with a test to prove our theory that BasicOrmMessageRepository isn't fast enough. We'll create TestFixtureSetup that configures BasicAppHost to wire up BasicOrmMessageRepository and a Setup method that we'll run before each test and that creates a large database of records to test against. Our test can then search the repository for a random record and time it to see how long it takes. We'll set higher bounds of 150 milliseconds for now. Here's how the code for our test looks:

    [TestFixture]
    public class BasicOrmMessageRepositoryPerformanceTests
    {
      const int LargeMessageCount = 100000;
      ServiceStackHost _appHost;
    
      [TestFixtureSetUp]
      public void FixtureSetUp()
      {
        _appHost = new BasicAppHost
        {
          ConfigureContainer =
            container =>
            {
              container.Register<IDbConnectionFactory>(
                new OrmLiteConnectionFactory(
                  "~/App_Data/db.sqlite".MapHostAbsolutePath(),
                  SqliteDialect.Provider));
              container
                .RegisterAutoWired<BasicOrmMessageRepository>();
            }
        }.Init();
      }
    
      [SetUp]
      public void SetUp()
      {
        const string testDataFile = 
        @"../../OrmMessageRepository_Performance_Test_Data.json";
    
        if (!File.Exists(testDataFile))
        {
          CreateTestFile(testDataFile, LargeMessageCount);
        }
    
        using (var db = _appHost
          .Resolve<IDbConnectionFactory>().OpenDbConnection())
        {
          db.DropAndCreateTable<Message>();
          var wholeList = File.ReadAllText(testDataFile)
            .FromJson<List<Message>>();
          db.InsertAll(wholeList);
        }
      }
    
      [TestFixtureTearDown]
      public void FixtureTearDown()
      {
        _appHost.Dispose();
      }
    
      [Test]
      public void ShouldHandleLargeMessageCountEffeciently()
      {
        var repo = _appHost.Resolve<BasicOrmMessageRepository>();
    
        var randomSearchString = "Message {0}".Fmt(
          new Random().Next(1, LargeMessageCount));
    
        var searchTimer = new Stopwatch();
        searchTimer.Start();
        var testSearchRecords = repo.Where(
          message => message
            .Body
            .Contains(randomSearchString));
        searchTimer.Stop();
    
        Assert.AreEqual(
          randomSearchString, 
          testSearchRecords.First().Body);
        Assert.Less(searchTimer.ElapsedMilliseconds, 150);
    
      }
      void CreateTestFile(string fileName, int testRecords)
      {
        Console.WriteLine("Creating test data...");
        var tmp = new List<Message>();
        foreach (int iteration in 
          Enumerable.Range(1, testRecords))
        {
          tmp.Add(new Message
          {
            Body = "Message {0}".Fmt(iteration),
            GroupName = "performance test group",
            Sender = "test sender"
          });
    
        }
    
        var wholeList = tmp.SerializeToString();
        File.WriteAllText(fileName, wholeList);
      }
    }

    Running this test should show a failure as with BasicOrmMessageRepository; we can't actually search through 100,000 records in less than 150 milliseconds. On my machine, it takes ~ 800 milliseconds, which will only get worse as we add messages.

  9. Now that we have a failing test, again we can set out to fix the problem. The best way to do this is to allow OrmLite to run our predicate in the database engine instead of downloading the whole database and running the predicate in memory. However, as we know, OrmLite's Select() method requires Expression<Func<Message,Bool>>, and right now we only accept a Func<Message,Bool> function. Let's start by refactoring the interface and the basic static list, then refactoring BasicOrmMessageRepository to make it a bit smarter:

    public interface IMessageRepository
    {
      void Add(Message message);
    
      IEnumerable<Message> Where(
        Expression<Func<Message, bool>> predicate);
    }
    public class StaticMessageRepository : IMessageRepository
    {
      static List<Message> _messages = 
        new List<Message>();
    
      public void Add(Message message)
      {
        _messages.Add(message);
      }
    
      public IEnumerable<Message> Where(
        Expression<Func<Message, bool>> predicate)
      {
        return_messages.TakeWhile(predicate.Compile()).ToList();
      }
    }
  10. Now that we've changed the method signature on the interface and updated our StaticMessageRepository implementation, we need to update BasicOrmMessageRepository too or our code won't compile. Here's how we do that:

    public class BasicOrmMessageRepository : IMessageRepository
    {
      public IDbConnectionFactory
        DbConnectionFactory { get; set; }
    
      public void Add(Message message)
      {
        using (var db = DbConnectionFactory.OpenDbConnection())
        {
          db.Insert(message);
        }
      }
    
      public IEnumerable<Message> Where(Expression<Func<Message, bool>> expression)
      {
        using (var db = DbConnectionFactory.OpenDbConnection())
        {
          return db.Select(expression);
        }
      }
    }
  11. With these changes, our original concurrency is solved, and so is our performance problem! Of course, our main service is still using StaticMessageRepository. Let's go back and wire it up in the production AppHost class:

    public class AppHost : AppHostBase
    {
      public AppHost() : base("Reidson Industries GroupMessenger",typeof(MessengerService).Assembly) { }
      public override void Configure(Funq.Container container)
      {
        var dbFactory = new OrmLiteConnectionFactory("~/App_Data/db.sqlite".MapHostAbsolutePath(),SqliteDialect.Provider);
    
        container.Register<IDbConnectionFactory>(dbFactory);
    
        container.RegisterAutoWiredAs<BasicOrmMessageRepository,IMessageRepository>();
    
        using (var db = dbFactory.OpenDbConnection())
        {
          db.DropAndCreateTable<Message>();
        }
      }
    }

How it works…

The basic pattern of this recipe is to build tests to prove the problem we're trying to solve, to build new implementations that pass the tests, then to use Funq IoC to switch implementations when new ones are ready. The previous code is a typical example of where we didn't necessarily plan ahead of time to make a component replaceable—we realized it as we recognized a problem. We showed realistic techniques to deal with this, making use of automated tests, interfaces, refactoring, and dependency injection.

While the Branch by Abstraction process might seem like it takes a little bit longer to get your code into the application, you reduce risk significantly by keeping your tests passing the whole time. You also reduce risk by not replacing something that works, albeit imperfectly, until your replacement is an actual improvement, all without needing to create a long-lived feature branch.