Using Dependency Injection to Decouple a Web Service

by Erik Lane 10. October 2006 09:04

I blogged about cutting my teeth on Mock-Objects.  They would've solved the need to make my code testable in isolation.  However, two requirements came into the picture:

1.  We needed a way to simulate the third-party web service that would be used for load testing.  Using a Mock-Object solutions doesn't provide this.
2.  Other groups need to be able to use my code but have the ability to configure the third-party web service at runtime.  This is real time code so Mock-Objects don't work for this either.

I'll Inject Your Dependency

My code is partially there as the configuration settings are stored in a configuration database.  So, for load testing and unit testing I could put the new configuration settings in the database and use the code I'm writing to simulate the third-party web service.  This doesn't solve my problem for other groups who have their own settings.  I am not going to maintain their settings and this is something that they will be telling me at runtime.

The functionality of my code and how it uses the web service will not change; who is using it and the configuration could change at runtime.  I'm trying really hard not to see this as just another nail but it looks like the right kind of nail to need the Dependency Injection hammer and I'm going to do it using the Strategy Pattern.  I am going to walk through the process to make my code meet the two aforementioned requirements.

Background on the structure of my code:  I have a factory that returns a class that implements the interface that has all of the properties and methods needed to interact with the third-party web service.  This class is what other parts of the application use because my code abstracts away a lot of details that they don't need to worry about.

Background on the changes I need to make:  There is already a lot of code that is using this code and we shouldn't have to modify it.  Yet, my code needs to accept a custom set of configuration settings to use during the calls to the web service.  I'm planning on having a "default" set of configuration settings that will be instantiated and used when no custom configuration is provided and also be able to plug-in any custom settings, at run-time.

I will have two tests just for this piece:
1.  Make sure I can still use my current code using a new "default" configuration.

2.  Use a custom configuration and see that the right value will be used.

Test #1

 public void UseDefaultConfigurationSettings()
 {
  ServiceApp app = ServiceFactory.CreateService();
  Assert.AreEqual(app.Configuration.ServiceUrl, "
http://localhost/DefaultService.asmx");
 }

Test #2

 public void InjectConfigurationSettings()
 {
  IServiceConfiguration configuration = new CustomConfiguration();
  configuration.ServiceUrl = "
http://localhost/CustomService.asmx";
  ServiceApp app = ServiceFactory.CreateService(configuration);
  Assert.AreEqual(app.Configuration.ServiceUrl, "
http://localhost/CustomService.asmx");
 }

Obviously, none of this will compile since I've not created the IServiceConfiguration interface or other objects/methods that relate to it.

First step is to create the interface:


 public interface IServiceConfiguration
 {
  string ServiceUrl { get; set; }
 }


Second step is to create my DefaultSettings class that will implement IServiceConfiguration and will read the database for the default settings.

 public class DefaultConfiguration : IRiskServiceConfiguration
 {
  private static string _serviceUrl = string.Empty;
  
  public DefaultConfiguration()
  {
   if(_serviceUrl == string.Empty)
   {
    _serviceUrl = GetUrlFromDatabase();
   }
  }
  
  public string ServiceUrl
  {
   get { return _serviceUrl; }
   set { _serviceUrl = value;}
  }
 }


Third step is to create a custom configuration class that will implement the Interface:

 public class CustomConfiguration : IServiceConfiguration
 {
  private string _serviceUrl
  
  public string ServiceUrl
  {
   get { return _serviceUrl; }
   set { _serviceUrl = value; }
  }
 }

Fourth and fifth steps are to modify my class(s) that implement IServiceApp to take an IServiceConfiguration in the constructor and to expose the IServiceConfiguration as a property.

 internal class ServiceApp : IServiceApp
 {
  private IServiceConfiguration _serviceConfiguration;
  
  public ServiceApp()
  {
   _serviceConfiguration = new DefaultConfiguration();
  }
  
  public ServiceApp(IServiceConfiguration customConfig)
  {
   _serviceConfiguration = customConfig;
  }
  
  public IServiceConfiguration Configuration
  {
   get { return _serviceConfiguration; }
  }
  
  ....
 }

This allows the custom settings to be injected into the app that will be making the calls to the physical web service.  I will keep the default constructor that doesn't take any arguments and instantiate the "default" configuration that will be read from the database.  This will keep existing code working without having to touch any of it.  Also, I'm making the property read-only so it forces use of the constructor to use a custom configuration (constructor injection).  This creates a valid object at construction time.

The final step, for me, was to overload the CreateService method on my ServiceFactory to take an IServiceConfiguration parameter and will instantiate a new service app based on the custom configuration.  Again, to keep current code working, I will keep the old method that doesn't take any parameters so the "default" configuration will be created.


 public class ServiceFactory
 {
  public static IService CreateService(IServiceConfiguration customConfig)
  {
   IService serviceApp = new ServiceApp(customConfig);
   return serviceApp;
  }
  
  public static IService CreateService()
  {
   IService serviceApp = new ServiceApp();
   return serviceApp;
  }

All of this will get my tests to compile and after working through the tests I get them to pass.  This my seem like a lot to just refactor some of the code but there or more pieces to the code that I'm not showing for brevity sake.  The main thing is that now I have a flexible piece of code that can be used for load testing and other applications.  Two requirements met.  :-)

I also wanted to add that I've got a lot of code in place as this refactoring is happening.  Writing my tests and having all of my current tests lets me feel that I'm meeting the new requirements and not breaking anything that already works.

Giddy Up!

Tags:

Comments

pingback
topsy.com on 8/22/2009 8:26:28 PM

Pingback from topsy.com

Twitter Trackbacks for
        
        Using Dependency Injection to Decouple a Web Service
        [eriklane.com]
        on Topsy.com

Comments are closed