locked
Scenario for Dependency Injection (Help needed) RRS feed

  • Question

  • Hello,

    I am very new to DI and Unity block. I've read alot about DI and IOC pattern but I've never used it in any of my application.  Now I'm starting to develop a new web based application using asp.net MVC. But I'm not sure if I should use DI in my Business Layer or not. Let me explain my scnario with a small example of my application. I have a class called Customer and another class called Address. I've a function in customer class responsible to population Address object and saving it into database.

     

    Class Customer

    {

    void SaveCustomerAddress()

    {

    Address address = new Address();

    CustomerDB db = new CustomerDB();

    address.street = "abcd";

    address.houseNo = "xyz"; //and there are other fileds i need to populate in address

    db.SaveCustomerAddress(add);

    }

    }

     

    Now my Customer class is dependent on Adress and CustomerDB class and I understand that I can use DI to inject those object from outside. But my question is what is the advantage of doing that? If I know that to SaveCustomerAddress, I always need and object of Address class so why do I depend on some external source to provide me with that object?

     

    Now I've another Scenario where I need to call database depending on the database provider (oralce, sqlserver etc). There I am confident that I should use DI as I'm not sure what type of provider (oracle or sqlserver) i would be using so it's better if I inject it from outside. But in my 1st Scenario, when I always know what object i want, y do i make my application more complex??

     

    I would really appreciate any help on this.

     

    Regards,

    TK Lee

    Wednesday, August 31, 2011 5:14 PM

Answers

  • In that example, yes it may help to have an IAccount interface and have it be injected into the Customer class.  It really depends on the complexity of the object and if it might constrain your ability to test to have the Customer class be dependent on a pure Account object instance.  One thing to start thinking about is test driven development.  Meaning, you think about how to create your tests first before even thinking about the implementation part yet (how to query the database, etc).  With that in mind, I personally would like to have an IAccount interface in this situation.  That way I can start with a very simple test object, for example a test object that tests how my Customer object handles getting the withdrawl limit, without being concerned with how I'm going to get the withdrawal limit yet (look it up in a database, call a web service, etc).  You don't want to deal with all that yet, all you want to deal with at first is making sure your Customer class can handle various return values from the GetWithdrawLimit method (for example how would it handle getting a negative number back??).  With that in mind you would probably do something like this:

     public interface IAccount
     {
      public decimal GetwithdrawlLimit(int customer_id);
     }
    
     public class Account_Production : IAccount
     {
      public decimal GetwithdrawalLimit(int customer_id)
      {
       //another developer might be working on the code to 
       //query the database and get the withdrawl limit
       //but the fact he or she hasn't finished this
       //part yet won't prevent you from testing 
       //the Customer object with a mock 
       //or fake Account object
      }
     }
    
     public class Account_Mock : IAccount
     {
      public decimal GetwithdrawalLimit(int customer_id)
      {
       return -500;
      }
     }
    
     public class Customer
     {
      public decimal WithdrawalLimit {get;set;}
    
      void SomeFunction(IAccount _acct)
      {
       //do something
    
       //do something
    
       IAccount acct = _acct;
    
       this.WithdrawalLimit =   acct.GetWithdrawalLimit(customer_id);
      }
     }
    
     [TestClass]
     class CustomerTestClass
     {
      [TestMethod]
      public void Test_if_Withdrawal_Limit_is_positive
      {
       Customer Cust = New Customer();
       IAccount mock = new Account_Mock();
       Cust.SomeFunction(mock);
           
       Assert.IsTrue(Cust.WithdrawalLimit > 0,"Test failed. Can't have a negative withdrawal limit.");
    
    
      }
     }
    
    }
    


     


    Tom Overton
    • Marked as answer by tkl33 Thursday, September 1, 2011 12:20 PM
    Thursday, September 1, 2011 6:04 AM

All replies

  • TK,

    What you would probably want to do is define an interface for your CustomerDB class (such as ICustomerDB) and have it be able to be "injected" into your method.  That way you can externally create any object that implements ICustomerDB into your SaveCustomerAddress method and test it.  So you could create a CustomerDB test object that implements ICustomerDB that reads from oracle, sql server, a set of in memory test data, etc. and pass it to your SaveCustomerAddress method, as long as it implements ICustomerDB. 

    public Interface ICustomerDB
    {
     ... 
    void SaveCustomerAddress (Address _address) 
    }
    
    Class Customer
    {
     void SaveCustomerAddress(ICustomerDB _customerDB)
     { Address address = new Address();
      CustomerDB db = _customerDB;
      address.street = "abcd";
      address.houseNo = "xyz";
     db.SaveCustomerAddress(add); 
     } 
    }
    
    


    Tom Overton

    • Edited by Tom_Overton Wednesday, August 31, 2011 6:00 PM fixed formatting and added text
    Wednesday, August 31, 2011 5:50 PM
  • Hello Tom,

    Thanks very much for your reply. Ok and what about Address class? Should I inject it from outside aswell instead of creating an object of Address inside my Customer class? Is there any advantage of injecting Address object from outside? Although I know that my SaveCustomerAdress function of Customer class will always need Address object so is there any point of injecting Address from outside?

     

    Thank you very much.

    Wednesday, August 31, 2011 6:57 PM
  • It's probably okay to keep your address class the way it is now, without having to inject as a dependancy and define an abstraction for it because your Address class looks like it's just an entity class that holds data properties.  If the Address class was connecting to a database then you would want to be able to inject it or create a mock Address class so you aren't dependant on some outside environment like a database while testing.


    Tom Overton
    Wednesday, August 31, 2011 7:34 PM
  • Hello Tom,

    Thank you very much for your response. I did understand everything you mention about ICustomerDB interface. But I'm still not satisfied why should I use DI even in that scenario. Because even if implementation of SaveCustomerAddress function (implemented from ICustomerDB interface) changes in the future, and if I'm creating my CustomerDB object inside Customer class, Customer class still doesn't have to change or re-test anyways because it's the CustomerDB's function that is changing. From what I understand is DI just instantiate your object and sends it to your main class from outside. I don't understand the advantage of using it. Even when you say that I can test that class independenty, means I still need some external source to Initiate CustomerDB, populate Address object and send it to SaveCustomerAddress() function of CustomerDB class.

    Thursday, September 1, 2011 1:19 AM
  • TK,

    In your simple example it may be hard to understand the usefulness of DI so let me illustrate how it would work.  I'll make a few changes to my previous example so it's setup in a more industry standard way.  As you can see we are able to create a CustomerDB object (which is really kind of your repository class) for different uses and test cases and test them if we wanted to using a test class.

    public interface ICustomerDB
    {
     void SaveCustomerAddress (Address _address);
    }
    
     public class CustomerDB_Oracle : ICustomerDB
     {
      public void SaveCustomerAddress(Address _address)
      {
        try
        {
           OracleConnection conn = new OracleConnection();
           OracleCommand cmd = new OracleCommand();
          //...Save data to Oracle database
        }
        catch
        {
          throw FailedSaveException;
        }
      }
    }
    
     public class CustomerDB_Sql : ICustomerDB
     {
      public void SaveCustomerAddress(Address _address)
      {
        try
        {
          SqlConnection conn = new SqlConnection();
          SqlCommand cmd = new SqlCommand();
          //...Save data to SQL Server database
        }
        catch
        {
          throw FailedSaveException;
        }
      }
    }
    
     public class CustomerDB_Mock : ICustomerDB
     {
      public void SaveCustomerAddress(Address _address)
      {
        try
        {
          //no DB connection needed, it just pretends to save
          //address to a database
          //...
        }
        catch
        {
          throw FailedSaveException;
        }
        
      }
    }
    
    
    public class Customer
    {
      private ICustomerDB db;
    
      public Customer()
      {
        
        
      }
    
      public Customer(ICustomerDB _customerDB)
      {
        db = _customerDB;
      }
    
      public void SaveCustomerAddress(Address _address)
      { 
        db.SaveCustomerAddress(_address); 
      } 
    }
    
     
     [TestClass]
     public class CustomerTestClass
     {
       
       [TestMethod]
       public void TestCallSaveCustomerAddress()
       {
        ICustomerDB mockDB = new CustomerDB_Mock();
        Address add = new Address();
        add.houseNo = "testhouse";
        add.street = "teststreet";
        Customer cust = new Customer(mockDB);
        try 
        {
          cust.SaveCustomerAddress(add);
          Assert.Fail("save seemed to work");
        }
        catch (Exception ex)
        {
           Assert.IsTrue(ex is SaveFailedException);
        }
     
    
       }
    
     }
    


     

     


    Tom Overton
    Thursday, September 1, 2011 3:46 AM
  • Thanks so very much for this effort. :) Yes your example does make sense to me. Now here in this customerDB example, because we could have any type of customerDB (customerdb_oracle, customerdb_sql etc), it makes sense to use DI. But what if you have 2 business classes and they are dependent on each other but there type doesn't change (unlike customerdb class where you could have customerdb_oracle, customerdb_sq). For example I've Customer class and I've Account class (account reflects to bank account)

     

    Class Customer
    {
    
    void SomeFunction()
    {
      //do something
    
      //do something
    
      Account acct = new Account();
    
      acct.GetWithdrawalLimit(customer_id);
    }
    
    }

    Now in this case, does it make sense to inject Account object from outside? Although you don't have multiple implementation of your Account class and your Customer class knows that you always need Account object to get WithDrawalLimit of a customer on this account.
    Thursday, September 1, 2011 4:33 AM
  • In that example, yes it may help to have an IAccount interface and have it be injected into the Customer class.  It really depends on the complexity of the object and if it might constrain your ability to test to have the Customer class be dependent on a pure Account object instance.  One thing to start thinking about is test driven development.  Meaning, you think about how to create your tests first before even thinking about the implementation part yet (how to query the database, etc).  With that in mind, I personally would like to have an IAccount interface in this situation.  That way I can start with a very simple test object, for example a test object that tests how my Customer object handles getting the withdrawl limit, without being concerned with how I'm going to get the withdrawal limit yet (look it up in a database, call a web service, etc).  You don't want to deal with all that yet, all you want to deal with at first is making sure your Customer class can handle various return values from the GetWithdrawLimit method (for example how would it handle getting a negative number back??).  With that in mind you would probably do something like this:

     public interface IAccount
     {
      public decimal GetwithdrawlLimit(int customer_id);
     }
    
     public class Account_Production : IAccount
     {
      public decimal GetwithdrawalLimit(int customer_id)
      {
       //another developer might be working on the code to 
       //query the database and get the withdrawl limit
       //but the fact he or she hasn't finished this
       //part yet won't prevent you from testing 
       //the Customer object with a mock 
       //or fake Account object
      }
     }
    
     public class Account_Mock : IAccount
     {
      public decimal GetwithdrawalLimit(int customer_id)
      {
       return -500;
      }
     }
    
     public class Customer
     {
      public decimal WithdrawalLimit {get;set;}
    
      void SomeFunction(IAccount _acct)
      {
       //do something
    
       //do something
    
       IAccount acct = _acct;
    
       this.WithdrawalLimit =   acct.GetWithdrawalLimit(customer_id);
      }
     }
    
     [TestClass]
     class CustomerTestClass
     {
      [TestMethod]
      public void Test_if_Withdrawal_Limit_is_positive
      {
       Customer Cust = New Customer();
       IAccount mock = new Account_Mock();
       Cust.SomeFunction(mock);
           
       Assert.IsTrue(Cust.WithdrawalLimit > 0,"Test failed. Can't have a negative withdrawal limit.");
    
    
      }
     }
    
    }
    


     


    Tom Overton
    • Marked as answer by tkl33 Thursday, September 1, 2011 12:20 PM
    Thursday, September 1, 2011 6:04 AM
  • Hello Tom,

    It really helped :) thanks very much for all your efforts I really appreciate it.

     

    Regards,

    Tk Lee

    Thursday, September 1, 2011 12:21 PM